Reputation: 60
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
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