Mahendra Singh
Mahendra Singh

Reputation: 60

TripleDES Encryption in java for decryption in C#

I have C# code which decrypts encrypted token passed by another application. I can not change this part. Now i'm writing an application in java which will encrypt my token, that would be passed to C# application.

I'm not able to match encrypted string with java code. Any help would be appreciated. Thank you.

C# Code

public class Crypto
{
    private TripleDES DesInstance = null;

    public Crypto(string key)
    {
        byte[] password = Encoding.GetEncoding(1252).GetBytes(key);
        DesInstance = new System.Security.Cryptography.TripleDESCryptoServiceProvider();
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, null);
        DesInstance.IV = new byte[8];
        DesInstance.Key = pdb.CryptDeriveKey("TripleDES", "SHA1", 192, DesInstance.IV);
    }

    public string Decrypt(string cipheredText)
    {
        byte[] cipherText = StringToByteArray(cipheredText);
        string plainText = null;

        ICryptoTransform transform = DesInstance.CreateDecryptor();

        MemoryStream memStreamEncryptedData = new MemoryStream(cipherText, 0, cipherText.Length - 1);
        CryptoStream encStream = new CryptoStream(memStreamEncryptedData, transform, CryptoStreamMode.Read);
        using (StreamReader srDecrypt = new StreamReader(encStream, Encoding.GetEncoding(1252)))
        {
            plainText = srDecrypt.ReadToEnd();
        }

        return plainText;
    }

    private byte[] StringToByteArray(string hex)
    {
        return Enumerable.Range(0, hex.Length)
                         .Where(x => x % 2 == 0)
                         .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                         .ToArray();
    }
}

Java Code

public class TripleDes {

    private static final String UNICODE_FORMAT = "UTF-8";
    public static final String DESEDE_ENCRYPTION_SCHEME = "DESede";
    private KeySpec ks;
    private SecretKeyFactory skf;
    private Cipher cipher;
    byte[] arrayBytes;
    private String myEncryptionKey;
    private String myEncryptionScheme;
    SecretKey key;

    public TripleDes() throws Exception {
        myEncryptionKey = "045e466ccc34a1f1688640d0441601b7ae2c";
        myEncryptionScheme = DESEDE_ENCRYPTION_SCHEME;
        arrayBytes = myEncryptionKey.getBytes(UNICODE_FORMAT);
        ks = new DESedeKeySpec(arrayBytes);
        skf = SecretKeyFactory.getInstance(myEncryptionScheme);
        cipher = Cipher.getInstance(myEncryptionScheme);
        key = skf.generateSecret(ks);
    }

    public String encrypt(String unencryptedString) {
        String encryptedString = null;
        try {
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] plainText = unencryptedString.getBytes(UNICODE_FORMAT);
            byte[] encryptedText = cipher.doFinal(plainText);
            encryptedString = new String(Base64.encodeBase64(encryptedText));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptedString;
    }

    public String decrypt(String encryptedString) {
        String decryptedText = null;
        try {
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] encryptedText = Base64.decodeBase64(encryptedString);
            byte[] plainText = cipher.doFinal(encryptedText);
            decryptedText = new String(plainText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decryptedText;
    }
}

Upvotes: 1

Views: 578

Answers (1)

Topaco
Topaco

Reputation: 49251

The C# code uses PasswordDeriveBytes#CryptDeriveKey() as key derivation function which is a wrapper around CAPI's CryptDeriveKey() function (see here). This functionality is missing in the Java code and is one of the reasons for the incompatibility.

PasswordDeriveBytes#CryptDeriveKey() is not to be confused with PasswordDeriveBytes#GetBytes(). The latter uses the (by MS) extended implementation of the PBKDF1 algorithm mentioned in the comments, which is not applied here.

The algorithm of CryptDeriveKey() is described here and an implementation can be found here:

public static byte[] CryptDeriveKey(byte[] hBaseData, String hashAlgorithm, int requiredLength) throws NoSuchAlgorithmException {
    int keyLength = hBaseData.length;
    byte[] derivedKey = new byte[requiredLength];
    if (keyLength >= requiredLength) {
        for (int i = 0; i < requiredLength; i++) {
            derivedKey[i] = hBaseData[i];
        }
        return derivedKey;
    }

    byte[] buff1 = new byte[64];
    byte[] buff2 = new byte[64];
    Arrays.fill(buff1, (byte) 0x36);
    Arrays.fill(buff2, (byte) 0x5C);
    for (int i = 0; i < keyLength; i++) {
        buff1[i] ^= hBaseData[i];
        buff2[i] ^= hBaseData[i];
    }

    MessageDigest md = MessageDigest.getInstance(hashAlgorithm);
    md.reset();
    byte[] result1 = md.digest(buff1);
    md.reset();
    byte[] result2 = md.digest(buff2);

    for (int i = 0; i < requiredLength; i++) {
        if (i < result1.length)
            derivedKey[i] = result1[i];
        else
            derivedKey[i] = result2[i - result1.length];
    }
    return derivedKey;
}

Note that CryptDeriveKey() does not expect the passphrase as first parameter, but the hash of the passphrase.

Example of usage:

String passphrase = "my passphrase";
String digestName = "SHA-1"; 
MessageDigest md = MessageDigest.getInstance(digestName);
byte[] hash = md.digest(passphrase.getBytes("Cp1252"));
byte[] key = CryptDeriveKey(hash, digestName, 24);
System.out.println(HexFormat.of().formatHex(key)); // 5de508082bfe0924356d881c8cde15a540c92f267ef5a416

Note that in the case of DES or TripleDES the parity bits are not adjusted. This happens implicitly in SecretKeyFactory#generateSecret().


Additional issues: Apart from key derivation, the CBC mode with a zero IV, PKCS#7 padding and the appropriate encodings for plaintext (Cp1252) and ciphertext (hex encoding) are to be used in the Java code. Since the C# code removes the last byte of the ciphertext for unknown reasons, a dummy byte must be added to the end of the ciphertext in the Java code.

Although the C# code can not be changed according to the description, the following notes on vulnerabilities: The key derivation is weak (this applies to both CryptDeriveKey() and PBKDF1) and should be replaced by a dedicated password-based key derivation like Argon2 or PBKDF2. A static IV (like a zero IV) is also weak. TripleDES is deprecated and slow and should be replaced with a modern algorithm like AES.

Upvotes: 1

Related Questions