CommonCrypto equivalency with Java Cipher - java

I am trying to decrypt a string encrypted via Apple's CommonCrypto API. It is using AES/CBC/PKCS7Padding. It takes the input string and a user provided password and generates the corresponding encrypted string which is Base64 encoded.
However, I have tried almost everything I found on SO on the related context but failed to decrypt the string. The issue is javax.crypto.BadPaddingException: pad block corrupted which essentially means a bad key. The iOS encryption manually pads a short key < 25 Bytes (in this case only) by appending a char 0. Now the CCryptStatus takes in the key length as 32 Bytes so my first question is that,
Despite providing a key less than the required length how is CommonCrypto encrypting the string with a short key?
Following are some specs of one such example
input String "XnV6f4YLAzkLvJ2CIJTC5g==2uXT5CSYKLUrL7+MkJ4IDw=="
_core _StringCore
userPassword String "123456789"
paddedKey String "1234567890000000000000000"
keyData NSData? 25 bytes 0x0000600000a4c360
numBytesEncrypted size_t 10
dataLength size_t 16
encryptedData NSMutableData? 10 bytes 0x0000604000842e20
ptrToData UnsafeMutableRawPointer
lengthOfData size_t 32
keyLength size_t 32
operation CCOperation 1
algoritm CCAlgorithm 0
options CCOptions 1
cryptStatus CCCryptorStatus 0
data NSString "Hello Jash" 0x0000600000631500
result String "Hello Jash"
Following is my decrypt method in Java using Cipher API based on a snippet found in a SO question.
String message = "XnV6f4YLAzkLvJ2CIJTC5g==2uXT5CSYKLUrL7+MkJ4IDw==";
String userKey = "123456789";
private static String decrypt (String message, String userKey) throws UnsupportedEncodingException,
NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidKeyException,
ShortBufferException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, NoSuchProviderException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
if (message.length() >= 48) {
ivFromEncryptedString = message.substring(0, Math.min(message.length(), 24));
messageFromEncryptedString = message.substring(24, message.length());
System.out.println(ivFromEncryptedString);
System.out.println(messageFromEncryptedString);
byte[] data = decodeBase64(messageFromEncryptedString);
byte[] ivData = decodeBase64(ivFromEncryptedString);
paddedKey = padShortKeys(userKey);
byte[] keyBytes = paddedKey.getBytes(CHARSET);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivData);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
byte [] encrypted = new byte[cipher.getOutputSize(data.length)];
int ctLength = cipher.update(data, 0, data.length, encrypted, 0);
ctLength += cipher.doFinal(encrypted, ctLength);
} catch (Exception e) {
System.out.println(e);
} finally {
return encrypted;
}
}
return null;
}
private static String encodeBase64(byte [] in){
return Base64.getEncoder().encodeToString(in);
}
private static byte[] decodeBase64(String str) throws UnsupportedEncodingException {
return DatatypeConverter.parseBase64Binary(str);
}
Can someone point me in the right direction what am I missing here? I am not a security expert or even intermediate individual. I have tried my best to grasp these concepts without diving into much detail. Is there any way to obtain same results in this Java code and decrypt the string?
I tried using SHA-256 for MessageDigest which turns the 25 Bytes padded key to 32 Bytes but the exception clearly indicates that there is something wrong with the key.

Related

Invalid AES key length: 12 bytes in java

As per given steps, I have performed encryption but am getting an error.
Generate a 16-digit random number (session key). Say RANDOMNO.
RANDOMNO = 1111222233334444
Encrypt RANDOMNO using RSA/ECB/PKCS1Padding and encode using Base64. Say ENCR_KEY.
ENCR_KEY = B64Encode(RSA/ECB/PKCS1Encryption(RANDOMNO,ICICIPubKey.cer))
Perform AES/CBC/PKCS5Padding encryption on request payload using RANDOMNO as key and ivinitialization vector. Say ENCR_DATA.
ENCR_DATA = B64Encode(AES/CBC/PKCS5Padding(REQUEST_DATA, RANDOMNO, IV))
Now the client may choose to send IV in request from one of the two options below.
Send Base64 Encoded IV in “iv” tag.
public byte[] generateRandomBytes() {
SecureRandom ng=new SecureRandom();
byte[] randomBytes=new byte[16];
ng.nextBytes(randomBytes);
return randomBytes;
}
//new method for encryption -we need to check
public String encryptRandomKeyWithCertificate(byte[] randomNumber) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, FileNotFoundException, CertificateException {
//step2: encrypt the random number with certificate
FileInputStream fin = new FileInputStream("D:\\cedge_uat\\ICICIUATpubliccert.cer");
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) f.generateCertificate(fin);
PublicKey publicKey = certificate.getPublicKey();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherData = cipher.doFinal(randomNumber);
String encodedData = Base64.getEncoder().encodeToString(cipherData);
return encodedData;
}
public String encryptRequestWithKey(String text, byte[] randomNumber) throws Exception {
//step3: encrypt the requestString with randomkeyEncrypted
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
BASE64Decoder decoder = new BASE64Decoder();
SecretKeySpec keySpec = new SecretKeySpec(randomNumber, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
cipher.init(Cipher.ENCRYPT_MODE, keySpec,ivSpec);
byte[] cipherData = cipher.doFinal(text.getBytes());
// BASE64Encoder encoder = new BASE64Encoder();
//return encoder.encode(cipherData).replaceAll("[\r\n]+", "");
String encodedData = Base64.getEncoder().encodeToString(cipherData);
return encodedData;
}
public static void main(String[] args) throws IOException {
String requestString = "CORP_USER=";
byte[] randomNumber;
String encryptedKey;
try {
randomNumber=encryption.generateRandomBytes();
encryptedKey = encryption.encryptRandomKeyWithCertificate(randomNumber);
String encryptedData =encryption.encryptRequestWithKey(requestString,randomNumber);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
According to the Wikipedia page on AES encryption, an AES key can be 128, 192 or 256 bits; i.e. 16, 24 or 32 bytes.
You are supplying a key whose size depends on the "random" number string you are generating. It looks like it will be between 1 and 19 digits plus a possible sign. When you call getBytes() on that string, you will get a byte array with anywhere between 1 and 20 bytes. That is typically NOT one of the acceptable key sizes for AES.
What you should do is use SecureRandom.nextBytes(byte[]) and supply a byte array of one of the three acceptable key sizes for AES.

Input length must be multiple of 16 when decrypting with padded cipher in GSTR7

How can I resolve this error when I run the following code?
String decrypted_appkey = "41+sD/gm9DWQeZbJm98qb3ss9Eu96XkClU5a4hyfaAw=";
String receivedSEK = "0x5D112907B134B9CE30E30745F48A536845521B04F6B912552AAA65B563F01CC0";
decryptedSek = NICEncrypt.decrypt(receivedSEK, decodeBase64StringTOByte(decrypted_appkey));
public static String decrypt(String plainText, byte[] secret)
throws InvalidKeyException, IOException, IllegalBlockSizeException,
BadPaddingException, Exception {
SecretKeySpec sk = new SecretKeySpec(secret, AES_ALGORITHM);
DECRYPT_CIPHER.init(Cipher.DECRYPT_MODE, sk);
byte[] bytes = DECRYPT_CIPHER.doFinal(Base64.getDecoder().decode(plainText));
return Base64.getEncoder().encodeToString(bytes);
}
private static byte[] decodeBase64StringTOByte(String stringData) throws Exception {
return java.util.Base64.getDecoder().decode(stringData.getBytes(CHARACTER_ENCODING));
}
Output:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher at
com.sun.crypto.provider.CipherCore.prepareInputBuffer(CipherCore.java:1005) at
com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:848) at
com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446) at
javax.crypto.Cipher.doFinal(Cipher.java:2164)
Commonly so called wrapped keys are not padded at all. What you have received may well be an encrypted 256 bit (AES) key. Usually you'd use "AES/ECB/NoPadding" for that, but without protocol specification that's just an informed guess. Note that - for the standard provider - the "AES" string defaults to "AES/ECB/PKCS5Padding" and will try to unpad.
Furthermore, your wrapped key is clearly not in base 64, it is in hexadecimals. You first need to decode the hexadecimals, and exclude the "0x" in front of the wrapped key.
receivedSEK is not in Base64, but seemingly some "hex" string.
String receivedSEK = "0x5D112907B134B9CE30E30745F48A536845521B04F6B912552AAA65B563F01CC0";
int n = (receivedSEK.length() - 2) / 2;
byte[] bytes = new byte[n];
for (int i = 0; i < n; ++i) {
bytes[i] = (byte) Integer.parseInt(receivedSEK.substring(2 + 2*i, 4 + 2*i), 16);
}
decryptedSek = NICEncrypt.decrypt(bytes, decodeBase64StringTOByte(decrypted_appkey));
public static String decrypt(byte[] bytes, byte[] secret)
throws InvalidKeyException, IOException, IllegalBlockSizeException,
BadPaddingException, Exception {
SecretKeySpec sk = new SecretKeySpec(secret, AES_ALGORITHM);
DECRYPT_CIPHER.init(Cipher.DECRYPT_MODE, sk);
byte[] bytes = DECRYPT_CIPHER.doFinal(bytes);
return Base64.getEncoder().encodeToString(bytes);
}
private static byte[] decodeBase64StringTOByte(String stringData) throws Exception {
return Base64.getDecoder().decode(stringData);
}
Also Base64 decoding is simpler.

javax.crypto.AEADBadTagException: Tag mismatch! Error when encrypting String

I wrote a simple Encryption and Decryption helper class for my android app to encrypt and store Strings securely.
It consists of a single static public method to encrypt, then it calls a private static method to decrypt the encrypted message and returns it. I wrote the method this way to check if the message is intact after encryption/decryption.
I wrote a simple JUnit test with a String and called AssertEquals on the String before and after sending it to the Crypto encryption method.
I get this following errors from running the test:
javax.crypto.AEADBadTagException: Tag mismatch!
The error stack:
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:571)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1046)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:983)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:845)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at util.Crypto.decrypt(Crypto.java:94)
at util.Crypto.encrypt(Crypto.java:64)
at com.example.ali.meappley.CryptoTest.encryptAndDecryptTest(CryptoTest.java:29)
I'm new to cryptography, but I read different stackoverflow replies and couldn't find anything of help. Some users suggested calling cipher.update(someByteArray) before calling cipher.doFinal(someByteArray) but I couldnt manage to get it working. Any suggestions?
This is my helper class
public class Crypto {
//public methods
//public static encrypt method
public static String encrypt(String messageToEncrypt, #Nullable byte[] associatedData) throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidAlgorithmParameterException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException {
byte[] plainBytes = messageToEncrypt.getBytes();
/////////////////////////////////////////////////////////////////
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = new SecretKeySpec(key, "AES");
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
if (associatedData != null) {
cipher.updateAAD(associatedData);
}
byte[] cipherText = cipher.doFinal(plainBytes);
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Arrays.fill(key,(byte) 0); //overwrite the content of key with zeros
///////////////////////////////////////////////////////////////////
byte[] decrypted = decrypt(cipherMessage, null, key);
return decrypted.toString();
}
//public static decrypt method
private static byte[] decrypt(byte[] cipherMessage, #Nullable byte[] associatedData, byte[] key) throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidAlgorithmParameterException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException {
ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);
int ivLength = byteBuffer.getInt();
if(ivLength < 12 || ivLength >= 16) { // check input parameter
throw new IllegalArgumentException("invalid iv length");
}
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
if (associatedData != null) {
cipher.updateAAD(associatedData);
}
cipher.update(cipherText);
byte[] plainText= cipher.doFinal(cipherText);
return plainText;
}
There are a few issues with your code:
1) In your encrypt-method remove the following line (or shift it behind the decrypt-call).
Arrays.fill(key, (byte) 0); // overwrite the content of key with zeros
Otherwise the key for encryption and decryption differ.
2) In your encrypt-method also pass the associatedData in your decrypt-call i.e. replace
byte[] decrypted = decrypt(cipherMessage, null, key);
with
byte[] decrypted = decrypt(cipherMessage, associatedData, key);
The associatedData passed for encryption and decryption have to match for validity. For the purpose of the associatedData see e.g. https://crypto.stackexchange.com/questions/6711/how-to-use-gcm-mode-and-associated-data-properly
3) In your decrypt-method remove the line
cipher.update(cipherText);
For the purpose of the update-method see e.g. What does cipher.update do in java?
All three issues give rise to an AEADBadTagException.
4) I suspect for testing purposes your encrypt-method returns decrypted.toString() which however only gives you the object's class and hashcode. It would make more sense to return e.g. new String(decrypted).

Decrypting CommonCrypto encrypted Base 64 encoded string in Java (AES/CBC/PKCS7Padding)

I am trying to decrypt a String with a known key in Java using standard Cipher API.
The encrypted String comes from a Web Service using the standard CommonCrypto Library which responds with some statistics as encrypted strings at regular intervals.
The specs are AES/CBC/PKCS7Padding with KeySize = 32 Bytes and BlockSize = 16 Bytes, and Encoding UTF-8 (raw) & Base64. I intend to write a Java client that can request these statistics, decrypt them and store them for later analyses.
Question 1. Does the CommonCrypto automatically pad keys with extra chars if the key is short? For instance less than 16 Bytes or 32 Bytes.
Question 2. What encoding measures should I take to ensure an identical encryption/decryption on both ends?
Example Strings and Key
String message = "mQp9sp8ri1E0V1Xfso1d5g==Mrf3wtaqUjASlZmUO+BI8MrWsrZSC0MxxMocswfYnqSn/VKB9luv6E8887eCxpLNNAOMB0YXv6OS7rFDFdlvC53pCHo3cVZiLJFqgWN/eNiC9p4RMxyFCcOzWrwKzT5P8sy55DwE25DNJkvMthSaxK5zcP1OdLgBiZFOSxYRsX4rBk7VP7p5xr2uTGjRL+jmGgB9u3TmeCNCr8NxGLNt6g==";
String userKey = "123456789";
private static String decrypt (String message, String userKey) throws UnsupportedEncodingException,
NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidKeyException,
ShortBufferException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, NoSuchProviderException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
if (message.length() >= 48) {
ivFromEncryptedString = message.substring(0, Math.min(message.length(), 24));
messageFromEncryptedString = message.substring(24, message.length());
System.out.println(ivFromEncryptedString);
System.out.println(messageFromEncryptedString);
byte[] data = decodeBase64(messageFromEncryptedString);
byte[] ivData = decodeBase64(ivFromEncryptedString);
paddedKey = padShortKeys(userKey);
byte[] keyBytes = paddedKey.getBytes(CHARSET);
MessageDigest sha = MessageDigest.getInstance("SHA-256"); //key
keyBytes = sha.digest(keyBytes);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivData);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
byte [] encrypted = new byte[cipher.getOutputSize(data.length)];
int ctLength = cipher.update(data, 0, data.length, encrypted, 0);
ctLength += cipher.doFinal(encrypted, ctLength);
} catch (Exception e) {
System.out.println(e);
} finally {
return encrypted;
}
}
return null;
}
private static String encodeBase64(byte [] in){
return Base64.getEncoder().encodeToString(in);
}
private static byte[] decodeBase64(String str) throws UnsupportedEncodingException {
return DatatypeConverter.parseBase64Binary(str);
}
Also with the current code status I am getting placehoder characters instead of the desired result.
Thanks in advance folks. :)
CommonCrypto is unclear, which implementation are you using? Apple, Apache, Java Class Cipher or another, please supply a link to it.
Never assume an encryption will pad the key or IV, they should always be provided in the exact length, there is no standard for such padding. If they need padding (they shouldn't) do it yourself.
Typically if encrypted data needs to be expressed as a character string Base64 encoding is used.
As James states, for one-shot encryption just use doFinal(ByteBuffer input, ByteBuffer output) which
encrypts or decrypts data in a single-part operation.
Note: A 9 digit key only has about 33-bits of security which is not close to sufficient. Simple using a hash function is insufficient for deriving an encryption key from a password, instead PBKDF2 or Argon2 should be used.

AES CBC No padding gives extra characters in decrypt JAVa

I am trying to encrypt and decrypt using AES/CBC/NoPadding in JAVA. I did the encryption in both JAVA and PHP using (mcrypt) and got the same result, using the same key and iv. However, when I try to decrypt in JAVA, I get the word correctly but always with extra characters. I read other questions and found that I need to add padding. So I added Padding5 but got the same result. Anyways, I need it without padding because that is how it works in PHP. Any help is appreciated. My code is below and the result is here:]2
public class RijndaelCrypt {
//private String key = "2a4e2471c77344b3bf1de28ab9aa492a444abc1379c3824e3162664a2c2b811d";
private static String iv = "beadfacebadc0fee";
private static String hashedKey = "6a2dad9f75b87f5bdd365c9de0b9c842";
private static Cipher cipher;
public static String decrypt(String text) throws UnsupportedEncodingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
SecretKeySpec keyspec = new SecretKeySpec(hashedKey.getBytes("UTF-8"), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes("UTF-8"));
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] decodedValue = Base64.decode(text.getBytes("UTF-8"));
byte[] decryptedVal = cipher.doFinal(decodedValue);
return new String(decryptedVal);
}
public static String encryptNew(String data) throws Exception {
cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
byte[] dataBytes = data.getBytes("UTF-8");
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}
byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(hashedKey.getBytes("UTF-8"), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes("UTF-8"));
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return DatatypeConverter.printBase64Binary(encrypted);
}
public static void main (String [] args) throws Exception
{
Security.addProvider(new BouncyCastleProvider());
String data = "Hello";
System.out.println("New Decrypted: " + RijndaelCrypt.decrypt(RijndaelCrypt.encryptNew(data)));
System.out.println("New Encryption: " + RijndaelCrypt.encryptNew(data));
}
}
The PHP mcrypt wrapper (or underlying mcrypt library) pads with zero bytes up to the block length (zero to 15 padding bytes, if 16 is the block size of the cipher). After that the blocks are encrypted by the cipher.
When decrypting in Java you need to manually remove any zero bytes from the right hand side of the plaintext after decryption using NoPadding. The zero valued padding bytes can of course be seen when hex-encoding the decrypted plaintext. However when outputting a string the zero bytes are either left out or converted to a replacement character (depending on the character set and terminal).
Note that the PHP zero padding has one big drawback: if the plaintext ends with one or more zero valued bytes it could be stripped from the decrypted plaintext by any unpadding routine. This is why PKCS#7 padding (which pads 1 to 16 bytes) should be preferred.
Also note that PHP actually needs rtrim("\0") to remove the zero bytes itself; mcrypt just leaves them there, but they generally won't be printed.
Note that Bouncy Castle crypto libraries also has ZeroPadding as option. However, this is zero padding of 1 to 16 bytes (i.e. it always pads/unpads) so it is incompatible with the padding defined used by PHP mcrypt and may fail if the size of the plaintext can be divided by the block size of the cipher.

Categories

Resources