Reputation: 43
I'm having issues to decrypt the ciphered text using BouncyCastle- the algorithm is AES-256CBC with PKC7 padding.
The PHP code that easily decrypts the data looks like that:
$output = 'WwBOU6s8DaMWmYdctBJwfuoujFgVygBUjhsbdf8eWqQ='; /* The encrypted return that we are going to decrypt */
$date = '2021-05-26 14:00:00'; /* The date which must correspond to the exact date transmitted during the call */
$private_key = '7X9gx9E3Qx4EiUdB63nc'; /* Your private key obtained when you created your API access */
$key = hash('sha256' , $date . $private_key); /* Creation of the key by concatenating the date and the private key */
$iv = mb_strcut(hash('sha256', hash('sha256', $private_key)), 0, 16,); /* Creation of the initialization vector (2x hash of the private key) by taking 16 bytes */
echo (openssl_decrypt($output, 'aes-256-cbc', $key, false, $iv)); /* Display the return using openssl_decrypt */
My attempt to achieve the same in Java look as follows:
public class Aes {
public static void main(String[] args) throws Exception {
String date = "2021-05-26 14:00:00";
String private_key = "7X9gx9E3Qx4EiUdB63nc";
String composite = date+private_key;
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(composite.getBytes());
byte[] stringHash = messageDigest.digest();
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(private_key.getBytes());
MessageDigest md2 = MessageDigest.getInstance("SHA-256");
md2.update(md.digest());
String encryptedText = "WwBOU6s8DaMWmYdctBJwfuoujFgVygBUjhsbdf8eWqQ=";
/*
* Register BC provider dynamically, BC needed for AES 256.
* AES 256 is Rijndael 128 using 32 byte key and 16 byte IV.
*/
Security.addProvider(new BouncyCastleProvider());
byte[] skey = stringHash;
//byte[] ivec = new byte[16]; // AES 256 use 16 byte IVEC
byte[] ivec=copyOfRange(md2.digest(), 0,16);
byte[] encrypted = javax.xml.bind.DatatypeConverter.parseBase64Binary(encryptedText);
if (encrypted.length % 16 != 0) {
throw new IllegalArgumentException("because has padding, input must be multiple of 16, length=" + encrypted.length);
}
PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(skey), ivec);
aes.init(false, ivAndKey); // false for decryption
int minSize = aes.getOutputSize(encrypted.length);
byte[] outBuf = new byte[minSize];
int length1 = aes.processBytes(encrypted, 0, encrypted.length, outBuf, 0);
int length2 = aes.doFinal(outBuf, length1);
int actualLength = length1 + length2;
byte[] decrypted = new byte[actualLength];
System.arraycopy(outBuf, 0, decrypted, 0, actualLength);
String decryptedString = new String(decrypted, "UTF-8");
System.out.println("<[" + decryptedString + "]>");
}
}
The problem is that I get the exception and I can't resolve it:
Exception in thread "main" org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
at org.bouncycastle.crypto.paddings.PKCS7Padding.padCount(Unknown Source)
at org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher.doFinal(Unknown Source)
at Aes.main(Aes.java:68)
I'm not sure where the issue is, is it when I tried to do the 2xhash of the private key or somewhere else.
Is there anyone capable of helping me out ?
Upvotes: 4
Views: 591
Reputation: 49141
The PHP code uses a digest for deriving the key and the IV. For this the default return value of the hash()
method is used, which does not return the data in binary, but hex encoded with lowercase letters. This both reduces security (since the value range of each byte is narrowed to 16 values) and makes the code less robust (since, depending on the implementation, a hexadecimal string can also use uppercase letters).
In addition, the hexadecimal encoding doubles the data size. As a result, the hex encoded key for SHA256 is 64 bytes in size and thus not a valid AES key. PHP corrects the key by silently truncating the key to 32 bytes for AES-256.
These aspects are not taken into account in the Java code. The key and IV must be derived in the Java code as follows to match those in the PHP code:
SHA256 is used as the digest:
MessageDigest md = MessageDigest.getInstance("SHA-256");
The key is determined as follows:
md.update(composite.getBytes());
byte[] skey = md.digest();
skey = Hex.toHexString(skey).substring(0, 32).getBytes(StandardCharsets.UTF_8); // Truncate key to 32 bytes
System.out.println("Key (hex): " + new String(skey, StandardCharsets.UTF_8)); // 0ff908ad560ccd391a927570b9a4956f
and the IV:
md.update(private_key.getBytes());
byte[] ivec = Hex.toHexString(md.digest()).getBytes(StandardCharsets.UTF_8); // Hex encode (conversion to lowercase not necessary, because Hex.toHexString() already uses lowercase)
md.update(ivec);
ivec = Hex.toHexString(md.digest()).substring(0, 16).getBytes(StandardCharsets.UTF_8); // Hex encode (conversion to lowercase not necessary, because Hex.toHexString() already uses lowercase), truncate IV to 16 bytes
System.out.println("IV (hex): " + new String(ivec, StandardCharsets.UTF_8)); // 18ca5cfff8d04d9b
Note that digest()
implicitly performs a reset. Hex.toHexString()
performs a hex encoding and is provided by BouncyCastle.
With this key and IV the ciphertext can be successfully decrypted with the Java code.
Beware also that key and IV derivation are insecure. A more secure pattern is to derive the key with a key derivation function like PBKDF2, and generate the IV randomly for each encryption.
Upvotes: 3