Matching iOS & Android AES - bad padding - android

I have a strange issue trying to match decryption between an existing iOS app, a .net server, and and Android app that I'm working on. I've checked that my program outputs byte for byte the same encryption as the iOS and decrypts it's own packets perfectly. It appears that the server is able to decrypt the packets sent from the Android app, but when I try to decode the packets from the server I'm getting a BadPaddingException on the Android, whereas the iOS version decodes properly. I've also checked that the Key and IV are byte identical.
edit: I've added the server side code (part of a UDP socket listener) from my client, at first glance it looks like padding has not been defined properly, but my research says the the default is PKCS7, so I'm still confused as to what's causing the problem.
I've tested message lengths (coming from the server) before and after decryption and I see 2 different messages. one is a null keep alive message of 16 bytes before decryption, 0 bytes after decryption. The second message is 128 bytes before decryption and 112 bytes after decryption, in iOS. Both fail to decrypt in Android.
iOS:
+ (NSData*)decryptData:(NSData*)data key:(NSData*)key iv:(NSData*)iv;
{
int FBENCRYPT_BLOCK_SIZE = kCCBlockSizeAES128;
int FBENCRYPT_KEY_SIZE = kCCKeySizeAES256;
// setup key
unsigned char cKey[FBENCRYPT_KEY_SIZE];
bzero(cKey, sizeof(cKey));
[key getBytes:cKey length:FBENCRYPT_KEY_SIZE];
// setup iv
char cIv[FBENCRYPT_BLOCK_SIZE];
bzero(cIv, FBENCRYPT_BLOCK_SIZE);
if (iv) {
[iv getBytes:cIv length:FBENCRYPT_BLOCK_SIZE];
}
NSData* Result = nil;
// setup output buffer
size_t bufferSize = [data length] + FBENCRYPT_BLOCK_SIZE;
void *buffer = malloc(bufferSize);
// do decrypt
size_t decryptedSize = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
cKey,
kCCKeySizeAES256,
cIv,
[data bytes],
[data length],
buffer,
bufferSize,
&decryptedSize);
if (cryptStatus == kCCSuccess) {
result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize];
} else {
free(buffer);
NSLog(#"[ERROR] failed to decrypt| CCCryptoStatus: %d", cryptStatus);
}
return result;
}
Android:
byte[] decryptData(byte[] data, byte[] key, byte[] iv)
{
static int FBENCRYPT_BLOCK_SIZE = 16; //(kCCBlockSizeAES128)
static int FBENCRYPT_KEY_SIZE = 32; //(kCCKeySizeAES256)
// setup key
byte[] cKey = new byte[FBENCRYPT_KEY_SIZE];
Arrays.fill(cKey, (byte) 0x00);
int num = FBENCRYPT_KEY_SIZE;
if (key.length<num)
num = key.length;
System.arraycopy(key, 0, cKey, 0, num);
// setup iv
byte[] cIv = new byte[FBENCRYPT_BLOCK_SIZE];
Arrays.fill(cIv, (byte) 0x00);
if (iv!=null) {
System.arraycopy(iv, 0, cIv, 0, iv.length);
}
Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
SecretKeySpec skeySpec = new SecretKeySpec(cKey, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(cIv);
aesCipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec);
byte[] byteCipherText = aesCipher.doFinal(data);
return byteCipherText;
}
Server C#:
public enum AESBitCounts
{
AES64Bit = 8,
AES128Bit = 16,
AES256Bit = 32
}
public static byte[] Encrypt(byte[] RawPayload, byte[] key, AESBitCounts AESBitCount)
{
Symmetric sym = new Symmetric(Symmetric.Provider.Rijndael, false);
//sym.mcrypto.Padding = System.Security.Cryptography.PaddingMode.None;
sym.IntializationVector = new Data(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 });
Data deckey = new Data();
deckey.MinBytes = (Int32)AESBitCount;
deckey.MaxBytes = (Int32)AESBitCount;
deckey.Bytes = key;
Byte fred = deckey.Bytes[0];
Data encrypted = sym.Encrypt(new Data(RawPayload), deckey);
return encrypted.Bytes;
}

It turns out that there is one crucial differance between CCCrypt and Cipher, CCCrypt will return whatever data it is able to decrypt, while Cipher, once it get's a padding error, will not return any data. It seems that my client's server side script was mangling the padding block so that any message shorter than 16 bytes was being decrypted on iOS as null, and in the case of the main message was simply dropping the padding block. I was able to duplicate the result of the iOS code by chopping the last 16 bytes before decoding, returning null if no bytes are left, and switching from using Cipher.doFinal to Cipher.update, which doesn't expect a final padding block to be present.
Edit: I actually didn't need to eliminate the last 16 bytes. Just switching from doFinal to update does the trick.

Related

How to encrypt data in android same way as done in angular using crypto-js

Code in angular using crypto-js:
let key = '12345123451234512345123451234509';// actual keys are different and has same length of 32 char
let iv = '12345123451234512345123451234509';
let secret_key = CryptoJS.enc.Hex.parse(key);
let secret_iv = CryptoJS.enc.Hex.parse(iv);
let encryptedString = CryptoJS.AES.encrypt(
JSON.stringify(data),
secret_key,
{
iv: secret_iv,
padding: CryptoJS.pad.ZeroPadding
}
).toString();
let requestObj = {
input: encryptedString.trim()
}
I am not able to do same encryption in android.
Android Code
String key32Char = "12345123451234512345123451234509";
String iv32Char = "12345123451234512345123451234509";
byte[] srcBuff = jsonString.getBytes("UTF-8");
//SecretKeySpec secretKeySpec = new SecretKeySpec(key32Char.getBytes(), "AES");
//IvParameterSpec ivParameterSpec = new IvParameterSpec(iv32Char.getBytes());
SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.decode(key32Char, Base64.NO_WRAP), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.decode(iv32Char, Base64.NO_WRAP));
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] dstBuff = cipher.doFinal(srcBuff);
String encryptedString = Base64.encodeToString(dstBuff, Base64.NO_WRAP);
JSONObject requestObj = new JSONObject();
requestObj.put("input", encryptedString);
What CryptoJS.enc.Hex.parse(key) line does ?
How to do same encryption ?
IV and Key:To match the key and IV part both must use either base64 or hex decodings.
In Hex encoded string there are 32 hex char that makes 128-bit. However, the same string can be rejected by base64 decode and if not rejected the output will not be 128-bit. You need to use
byte[] bytes = new BigInteger("7F" + str, 16).toByteArray();
SecretKeySpec key = new SecretKeySpec(bytes, 1, bytes.length-1, "AES");
to convert the hex string into byte array.
padding: CryptoJS.pad.ZeroPadding is useful if your data size is an exact multiple of 128. Otherwise, you need to use this parameter to say that I'll use this for testing my new padding scheme. You need to better use Pkcs7 that was the default.
In Java you need getInstance("AES/CBC/PKCS5Padding");
Mode of operation: The default in JS is CBC, therefor you need the same in Java, as above getInstance("AES/CBC/PKCS5Padding");
Output: To compare the outputs you need to see the same result. In Java you convert the output into base64, so you need the same for JS.
As you can see, you must do the same steps, parameters for both.
Note that: CBC mode is archaic and you should prefer authenticated encryption modes like AES-GCM or ChaCha20-Poly1305. They not only provides confidentiality but also integrity and authentication. Unfortunately, crypto-js doesn't have them. But you can use some other JS libraries for that.
As name suggests, CryptoJS.enc.Hex.parse(key) parses a Hex String and uses it as key. So you need to do the same for your java code.
In addition, you need to select correct encryption mode and padding too. Your CryptoJs code uses CBC mode so you need to do same in Java Code. Your are using zero padding in CryptoJs side which is not available in java, so you need to do it manually. But in general, using zero padding is a bad idea and it is better to use PKCS5 padding for example which is default for CryptoJs.
With these things, these 2 codes match:
let key = '12345123451234512345123451234509';// actual keys are different and has same length of 32 char
let iv = '12345123451234512345123451234509';
let secret_key = CryptoJS.enc.Hex.parse(key);
let secret_iv = CryptoJS.enc.Hex.parse(iv);
let encryptedString = CryptoJS.AES.encrypt(
"0123456789012345x",
secret_key,
{
iv: secret_iv,
}
).toString();
let requestObj = {
input: encryptedString.trim()
}
Java:
public void doit()
{
byte[] key32Char = hexStringToByteArray("12345123451234512345123451234509");
byte[] iv32Char = hexStringToByteArray("12345123451234512345123451234509");
byte[] srcBuff = "0123456789012345x".getBytes();
SecretKeySpec secretKeySpec = new SecretKeySpec(key32Char, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv32Char);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] dstBuff = cipher.doFinal(srcBuff);
String encryptedString = new String(Base64.getEncoder().encode(dstBuff));
System.out.print(encryptedString);
}
catch(Exception e) {
System.out.print(e.toString());
return;
}
}
public byte[] hexStringToByteArray(String s)
{
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
Update:
If you are forced to use bad idea of zero padding, you need to keep real size of data and do padding manually:
public void doitZeroPadding()
{
...
// For the simplicity, I assume that data size is smaller than 128.
// You need to change this part as needed.
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int dsize = srcBuff.length + 1; // 1 is for plain buffer size
// This line align size to the multiple of block size.
int newBufSize = ((dsize + cipher.getBlockSize() - 1) / cipher.getBlockSize()) * cipher.getBlockSize();
byte[] newSrcBuf = new byte[newBufSize];
// You need real buffer size, or you don't know how long is decrypted buffer.
// I add it inside encrypting buffer to prevent other to see real decrypted buffer size.
// But if you want to have exact same encrypted buffer on both sides, you must remove it.
newSrcBuf[0] = (byte)(srcBuff.length);
System.arraycopy(srcBuff, 0, newSrcBuf, 1, srcBuff.length);
// Now use newSrcBuf/newBufSize
...
}
on the decryption side, check real size from decrypted buffer and use that size starting byte 1 for creating string.

Decrypt with AES (MODE_CBC/NoPadding) a file in iOS encrypted in Python

I receive from a Server in Python a file encrypted in this manner:
import os
from Crypto.Cipher import AES
from Crypto import Random
def pad(s):
return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(message, key):
message = pad(message)
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def encrypt_file(file_name, key):
with open("epub/" + file_name, 'rb') as fo:
plaintext = fo.read()
enc = encrypt(plaintext, key)
file_name = "enc/" + file_name
with open(file_name, 'wb') as fo:
fo.write(enc)
for list in os.listdir('./epub'):
if list.find('.epub') != -1:
key = '0123456789012345'
encrypt_file(list, key)
My App in Android decrypt file in this manner:
FileInputStream epubCifr = new FileInputStream(Environment.getExternalStorageDirectory() + "/Skin/readium/epub_content/" + title + "/" + title + ".epub");
FileOutputStream epubDec = new FileOutputStream(Environment.getExternalStorageDirectory() + "/Skin/readium/epub_content/" + title + "/" + title + "_dec.epub");
SecretKeySpec sks = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, sks);
CipherInputStream cis = new CipherInputStream(epubCifr, cipher);
int b;
byte[] d = new byte[16];
while ((b = cis.read(d)) != -1)
epubDec.write(d, 0, b);
epubDec.flush();
epubDec.close();
cis.close();
Now.
I would do the same thing in an app for iOS but I'm a newbie in objective C.
I can't change code on Server.
I tried to use RNCrypto for iOS but with poor results.
Any suggestions?
Thanks
EDIT 1
Sorry. This is my code in objective C.
I think that there are some settings in RNCryptor that are wrong.
int blockSize = 16;
NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:#"epub.epub"];
NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:#"epub_dec.epub" append:NO];
[cryptedStream open];
[decryptedStream open];
__block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
__block RNEncryptor *decryptor = nil;
dispatch_block_t readStreamBlock = ^{
[data setLength:blockSize];
NSInteger bytesRead = [cryptedStream read:[data mutableBytes] maxLength:blockSize];
if (bytesRead < 0) {
}
else if (bytesRead == 0) {
[decryptor finish];
}
else {
[data setLength:bytesRead];
[decryptor addData:data];
NSLog(#"Sent %ld bytes to decryptor", (unsigned long)bytesRead);
}
};
decryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings
password:#"0123456789012345"
handler:^(RNCryptor *cryptor, NSData *data) {
NSLog(#"Decryptor recevied %ld bytes", (unsigned long)data.length);
[decryptedStream write:data.bytes maxLength:data.length];
if (cryptor.isFinished) {
[decryptedStream close];
}
else {
readStreamBlock();
}
}];
readStreamBlock();
EDIT 2
Python code with PKCS7 and iv of 0 bytes.
from Crypto.Cipher import AES
from Crypto import Random
from pkcs7 import PKCS7Encoder
def pad(s):
encoder = PKCS7Encoder()
return encoder.encode(s)
#return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(message, key):
message = pad(message)
#iv = Random.new().read(AES.block_size)
iv = b'0000000000000000'
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def encrypt_file(file_name, key):
with open("epub/" + file_name, 'rb') as fo:
plaintext = fo.read()
enc = encrypt(plaintext, key)
file_name = "enc/" + file_name
with open(file_name, 'wb') as fo:
fo.write(enc)
key = '0123456789012345'
encrypt_file("epub1.epub", key)
decrypt_file("epub1.epub", key)
RNCryptor in this case is not a good choice because it imposes a format and method for encryption that does not match the method being used to encrypt.
You state that there is no padding but the Python code does pad the message with zero bytes, this is both non-standard, insecure and assumes that the data being encrypted does not end with a null byte. You will have to handle stripping the null padding after decryption. The usual padding is PKCS#7.
The iv for CBC mode is prepended to the encrypted data, this is a good scheme. You will need to separate the iv from the encryption data and apply it to the decryption function call.
It is not clear that the encryption key is exactly the correct length or what AES key size is being used. Presumably the Python method is using the length of the key to determine the AES key size, it can be 128, 192 or 256 bits and if not one of these padding the key to length. You will need to determine this and handle it in your code. The AES key size should really be specified and the key should be exactly the matching length.
With the above you can use the Apple supplied Common Crypto, specifically CCCrypt function.
It is potentially possible to modify the received data to match the requirements of RNCryptor and handle the padding after the decryption.

CryptographicException: Bad PKCS7 padding

I am seeing a small percentage of production users randomly report this exception related to encrypting/decrypting strings with Xamarin.Android but unfortunately I cannot reproduce it.
What could cause this and/or how could I reproduce the exception so that I can figure out a fix/workaround?
[CryptographicException: Bad PKCS7 padding. Invalid length 147.]
Mono.Security.Cryptography.SymmetricTransform.ThrowBadPaddingException(PaddingMode padding, Int32 length, Int32 position):0
Mono.Security.Cryptography.SymmetricTransform.FinalDecrypt(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0
Mono.Security.Cryptography.SymmetricTransform.TransformFinalBlock(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0
System.Security.Cryptography.CryptoStream.FlushFinalBlock():0
com.abc.mobile.shared.Security+PasswordEncoder.DecryptWithByteArray(System.String strText, System.String strEncrypt):0
EDIT: Here's the code I am using to encrypt/decrypt
private string EncryptWithByteArray(string inPassword, string inByteArray)
{
byte[] tmpKey = new byte[20];
tmpKey = System.Text.Encoding.UTF8.GetBytes(inByteArray.Substring(0, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] inputArray = System.Text.Encoding.UTF8.GetBytes(inPassword);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(tmpKey, mInitializationVector), CryptoStreamMode.Write);
cs.Write(inputArray, 0, inputArray.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
}
private string DecryptWithByteArray (string strText, string strEncrypt)
{
try
{
byte[] tmpKey = new byte[20];
tmpKey = System.Text.Encoding.UTF8.GetBytes (strEncrypt.Substring (0, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider ();
Byte[] inputByteArray = Convert.FromBase64String (strText);
MemoryStream ms = new MemoryStream ();
CryptoStream cs = new CryptoStream (ms, des.CreateDecryptor (tmpKey, mInitializationVector), CryptoStreamMode.Write);
cs.Write (inputByteArray, 0, inputByteArray.Length);
try {
cs.FlushFinalBlock();
} catch (Exception ex) {
throw(ex);
}
System.Text.Encoding encoding = System.Text.Encoding.UTF8;
return encoding.GetString(ms.ToArray());
}
catch (Exception ex)
{
throw ex;
}
}
EDIT 2:
The encryption key is always the local Device ID. Here's how I am getting this:
TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager;
string deviceId = telephonyMgr.DeviceId == null ? "UNAVAILABLE" : telephonyMgr.DeviceId;
Here's an example of how it's called:
string mByteArray = GetDeviceId();
string mEncryptedString = EncryptWithByteArray(stringToEncrypt, mByteArray);
string mDecryptedString = DecryptWithByteArray(mEncryptedString, mByteArray);
You have not provided much details about your use case but I would say this is happening because you are not using the same cipher settings during the encryption and decryption operations. Symmetric ciphers require you to use exactly the same settings/parameters during the data encryption and also decryption. For example for AES CBC you would need to use exactly the same key, IV, cipher mode and padding on both devices. It is best to set these setting explicitly in the code:
System.Security.Cryptography.RijndaelManaged aes = new System.Security.Cryptography.RijndaelManaged();
aes.Key = new byte[] { ... };
aes.IV = new byte[] { ... };
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
If you are sure you are using the same settings then you should also consider scenario that some data get corrupted or altered during the network transfer.
Edit after some code fragments have been provided:
Decryption method you have provided does not work for me at all so I have put together all your samples and turned them into the code which does the same thing as yours but uses IMO a slightly cleaner approach. For example this code uses more robust "key derivation" (please forgive me cryptoguys) and it has also passed basic code analysis.
You should be able to easily use public methods to do what you need:
string plainData = "This information should be encrypted";
string encryptedData = EncryptStringified(plainData);
string decryptedData = DecryptStringified(encryptedData);
if (plainData != decryptedData)
throw new Exception("Decryption failed");
Implementation and private methods follows:
/// <summary>
/// Encrypts string with the key derived from device ID
/// </summary>
/// <returns>Base64 encoded encrypted data</returns>
/// <param name="stringToEncrypt">String to encrypt</param>
public string EncryptStringified(string stringToEncrypt)
{
if (stringToEncrypt == null)
throw new ArgumentNullException("stringToEncrypt");
byte[] key = DeviceIdToDesKey();
byte[] plainData = Encoding.UTF8.GetBytes(stringToEncrypt);
byte[] encryptedData = Encrypt(key, plainData);
return Convert.ToBase64String(encryptedData);
}
/// <summary>
/// Decrypts Base64 encoded data with the key derived from device ID
/// </summary>
/// <returns>Decrypted string</returns>
/// <param name="b64DataToDecrypt">Base64 encoded data to decrypt</param>
public string DecryptStringified(string b64DataToDecrypt)
{
if (b64DataToDecrypt == null)
throw new ArgumentNullException("b64DataToDecrypt");
byte[] key = DeviceIdToDesKey();
byte[] encryptedData = Convert.FromBase64String(b64DataToDecrypt);
byte[] decryptedData = Decrypt(key, encryptedData);
return Encoding.UTF8.GetString(decryptedData);
}
private byte[] DeviceIdToDesKey()
{
TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager;
string deviceId = telephonyMgr.DeviceId ?? "UNAVAILABLE";
// Compute hash of device ID so we are sure enough bytes have been gathered for the key
byte[] bytes = null;
using (SHA1 sha1 = SHA1.Create())
bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(deviceId));
// Get last 8 bytes from device ID hash as a key
byte[] desKey = new byte[8];
Array.Copy(bytes, bytes.Length - desKey.Length, desKey, 0, desKey.Length);
return desKey;
}
private byte[] Encrypt(byte[] key, byte[] plainData)
{
if (key == null)
throw new ArgumentNullException("key");
if (plainData == null)
throw new ArgumentNullException("plainData");
using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
{
if (!desProvider.ValidKeySize(key.Length * 8))
throw new CryptographicException("Key with invalid size has been specified");
desProvider.Key = key;
// desProvider.IV should be automatically filled with random bytes when DESCryptoServiceProvider instance is created
desProvider.Mode = CipherMode.CBC;
desProvider.Padding = PaddingMode.PKCS7;
using (MemoryStream encryptedStream = new MemoryStream())
{
// Write IV at the beginning of memory stream
encryptedStream.Write(desProvider.IV, 0, desProvider.IV.Length);
// Perform encryption and append encrypted data to the memory stream
using (ICryptoTransform encryptor = desProvider.CreateEncryptor())
{
byte[] encryptedData = encryptor.TransformFinalBlock(plainData, 0, plainData.Length);
encryptedStream.Write(encryptedData, 0, encryptedData.Length);
}
return encryptedStream.ToArray();
}
}
}
private byte[] Decrypt(byte[] key, byte[] encryptedData)
{
if (key == null)
throw new ArgumentNullException("key");
if (encryptedData == null)
throw new ArgumentNullException("encryptedData");
using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
{
if (!desProvider.ValidKeySize(key.Length * 8))
throw new CryptographicException("Key with invalid size has been specified");
desProvider.Key = key;
if (encryptedData.Length <= desProvider.IV.Length)
throw new CryptographicException("Too short encrypted data has been specified");
// Read IV from the beginning of encrypted data
// Note: New byte array needs to be created because data written to desprovider.IV are ignored
byte[] iv = new byte[desProvider.IV.Length];
Array.Copy(encryptedData, 0, iv, 0, iv.Length);
desProvider.IV = iv;
desProvider.Mode = CipherMode.CBC;
desProvider.Padding = PaddingMode.PKCS7;
// Remove IV from the beginning of encrypted data and perform decryption
using (ICryptoTransform decryptor = desProvider.CreateDecryptor())
return decryptor.TransformFinalBlock(encryptedData, desProvider.IV.Length, encryptedData.Length - desProvider.IV.Length);
}
}
It is really hard to tell what exactly was problem with your code because your decryption method did not work for me at all - most likely because it is using CryptoStream in write mode for decryption which seems a little odd to me.
So much for the code. Now let's get to encryption which is really really weak. It is more just an obfuscation that should protect the data from being accidentally displayed in plain text form (some people use BASE64 encoding for the same thing). The main cause of this is relatively old encryption algorithm and easily predictable encryption key. AFAIK every application running on the same device can read device ID without any privileges. That means any application can decrypt your data. Of course your SQLite database is probably accessible only to your application but that can no longer be true if you remove the SD card or root your phone. To make this a little better you could for example ask user to provide a password and then use it to derive unique encryption key but that is completely different problem. Anyway I am not really sure what you are trying to achieve with this encryption - it may be fully sufficient for your needs even if it can be considered to be weak.
Hope this helps.

Equivalent of spongycastle encryption for ios

This has stumped me - the following code uses SpongyCastle's encryption/decryption for Android - I am trying to achieve cross-platform encryption/decryption for iOS.
The following code (from Android) works a treat, AES 128bit CBC with PKCS7Padding, using a supplied salt and password, which the salt is stored on the mysql database, the password is by the end-user, the following code is adapted from this answer by kelhoer.
The reason I used AES128bit is that AES256 is not available in iOS 4+, it was introduced in iOS5+, and having to dip the toe into using openssl to generate a derived key and initialization vector (iv), it was dicey as learnt that Apple rejects apps that are statically linked with openssl library.
Since the platform is based on iOS 4.2+, having resorted to bundling and statically linking the openssl library seems rather, over-kill and would preferably use the CommonCryptor library.
Here's the Android version with Spongycastle code in place:
private static void encrypt(InputStream fin,
OutputStream fout,
String password,
byte[] bSalt) {
try {
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(
new SHA256Digest()
);
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes =
PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, bSalt, ITERATIONS);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams =
(ParametersWithIV) pGen.generateDerivedParameters(128, 128);
aesCBC.init(true, aesCBCParams);
PaddedBufferedBlockCipher aesCipher =
new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
aesCipher.init(true, aesCBCParams);
byte[] buf = new byte[BUF_SIZE];
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = fin.read(buf)) >= 0) {
if (numRead == 1024) {
byte[] plainTemp = new byte[
aesCipher.getUpdateOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
final byte[] plain = new byte[offset];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
} else {
byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
}
}
fout.close();
fin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void decrypt(InputStream fin,
OutputStream fout,
String password,
byte[] bSalt) {
try {
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(
new SHA256Digest()
);
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes =
PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, bSalt, ITERATIONS);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams =
(ParametersWithIV) pGen.generateDerivedParameters(128, 128);
aesCBC.init(false, aesCBCParams);
PaddedBufferedBlockCipher aesCipher =
new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
aesCipher.init(false, aesCBCParams);
byte[] buf = new byte[BUF_SIZE];
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = fin.read(buf)) >= 0) {
if (numRead == 1024) {
byte[] plainTemp = new byte[
aesCipher.getUpdateOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
// int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
} else {
byte[] plainTemp = new byte[
aesCipher.getOutputSize(numRead)];
int offset =
aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
}
}
fout.close();
fin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
However under iOS 4.2 (Working with XCode) I cannot figure out how to do the equivalent,
This is what I have tried under Objective C, with the goal of decrypting data from the Android side, stored in mysql database, to test this out:
+(NSData*) decrypt:(NSData*)cipherData
userPassword:(NSString*)argPassword
genSalt:(NSData*)argPtrSalt{
size_t szPlainBufLen = cipherData.length + (kCCBlockSizeAES128);
uint8_t *ptrPlainBuf = malloc(szPlainBufLen);
//
const unsigned char *ptrPasswd =
(const unsigned char*)[argPassword
cStringUsingEncoding:NSASCIIStringEncoding];
int ptrPasswdLen = strlen(ptrPasswd);
//
NSString *ptrSaltStr = [[NSString alloc]
initWithData:argPtrSalt
encoding:NSASCIIStringEncoding];
const unsigned char *ptrSalt =
(const unsigned char *)[ptrSaltStr UTF8String];
NSString *ptrCipherStr =
[[NSString alloc]initWithData:cipherData
encoding:NSASCIIStringEncoding];
unsigned char *ptrCipher = (unsigned char *)[ptrCipherStr UTF8String];
unsigned char key[kCCKeySizeAES128];
unsigned char iv[kCCKeySizeAES128];
//
//int EVP_BytesToKey(const EVP_CIPHER *type,const EVP_MD *md,
//const unsigned char *salt, const unsigned char *data,
//int datal, int count, unsigned char *key,unsigned char *iv);
int i = EVP_BytesToKey(EVP_aes_128_cbc(),
EVP_sha256(),
ptrSalt,
ptrPasswd,
ptrPasswdLen,
PBKDF2_ITERATIONS,
key,
iv);
NSAssert(i == kCCKeySizeAES128,
#"Unable to generate key for AES");
//
size_t cipherLen = [cipherData length];
size_t outlength = 0;
//
CCCryptorStatus resultCCStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key,
kCCBlockSizeAES128,
iv,
ptrCipher,
cipherLen,
ptrPlainBuf,
szPlainBufLen,
&outlength);
NSAssert(resultCCStatus == kCCSuccess,
#"Unable to perform PBE AES128bit decryption: %d", errno);
NSData *ns_dta_PlainData = nil;
if (resultCCStatus == kCCSuccess){
ns_dta_PlainData =
[NSData dataWithBytesNoCopy:ptrPlainBuf length:outlength];
}else{
return nil;
}
return ns_dta_PlainData;
}
Have supplied the data and user's password and get a return code from CCCrypt as -4304 which indicates not successful and bad decoding.
I have thought that perhaps the encoding scheme would be throwing off the CommonCryptor's decryption routing hence the long-winded way of converting to NSASCIIStringEncoding.
The Salt is stored along with the cipher data, and is 32bytes in length.
What am I missing in this regard, bearing in mind, am weak on cryptography.
I have taken the liberty in coding a direct port of the PKCS12Parameters generator as used on the Android side, the gist for this header is above.
The implementation is direct copy also, as found here, the password, is converted to PKCS12 equivalent - unicode, big-endian, with two extra zeros padded on to the end.
The Generator generates the derived key and iv via performing the number of iterations, in this case, 1000, as is on the Android side, using the SHA256 Digest, the final generated key and iv is then used as the parameters to CCCryptorCreate.
Using the following code sample is not working either, it ends with -4304 upon a call to CCCryptorFinal
The code excerpt is as shown:
#define ITERATIONS 1000
PKCS12ParametersGenerator *pGen = [[PKCS12ParametersGenerator alloc]
init:argPassword
saltedHash:argPtrSalt
iterCount:ITERATIONS
keySize:128
initVectSize:128];
//
[pGen generateDerivedParameters];
//
CCCryptorRef decryptor = NULL;
// Create and Initialize the crypto reference.
CCCryptorStatus ccStatus = CCCryptorCreate(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
pGen.derivedKey.bytes,
kCCKeySizeAES128,
pGen.derivedIV.bytes,
&decryptor
);
NSAssert(ccStatus == kCCSuccess,
#"Unable to initialise decryptor!");
//
size_t szPlainBufLen = cipherData.length + (kCCBlockSizeAES128);
// Calculate byte block alignment for all calls through to and including final.
size_t szPtrPlainBufSize = CCCryptorGetOutputLength(decryptor, szPlainBufLen, true);
uint8_t *ptrPlainBuf = calloc(szPtrPlainBufSize, sizeof(uint8_t));
//
// Set up initial size.
size_t remainingBytes = szPtrPlainBufSize;
uint8_t *ptr = ptrPlainBuf;
size_t movedBytes = 0;
size_t totalBytesWritten = 0;
// Actually perform the encryption or decryption.
ccStatus = CCCryptorUpdate(decryptor,
(const void *) cipherData.bytes,
szPtrPlainBufSize,
ptr,
remainingBytes,
&movedBytes
);
NSAssert(ccStatus == kCCSuccess,
#"Unable to update decryptor! Error: %d", ccStatus);
ptr += movedBytes;
remainingBytes -= movedBytes;
totalBytesWritten += movedBytes;
//
// Finalize everything to the output buffer.
CCCryptorStatus resultCCStatus = CCCryptorFinal(decryptor,
ptr,
remainingBytes,
&movedBytes
);
totalBytesWritten += movedBytes;
if(decryptor) {
(void) CCCryptorRelease(decryptor);
decryptor = NULL;
}
NSAssert(resultCCStatus == kCCSuccess,
#"Unable to perform PBE AES128bit decryption: %d", resultCCStatus);
The funny thing, the decryption works, the final call to CCCryptorFinal returns 0 if I substitute the kCCOptionPKCS7Padding for 0x0000 at the start of CCCryptorCreate, i.e no padding. Alas, the data is not what I expected, still totally scrambled regardless when that "does not work".
It is failing somewhere, so if anyone has any better ideas on how to achieve the equivalent, I would be delighted to hear other opinions.
Either its change the mechanism on the Android side to make it "cross-platform" compatible with iPhone or seek an alternative cryptographic solution to be compatible on both ends at the expense of weaker cryptography on both sides of platforms used for making data interchange portable.
The input data as supplied:
Base64 encoded cipher, with the salt and the cipher separated by ':' tnNhKyJ2vvrUzAmtQV5q9uEwzzAH63sTKtLf4pOQylw=:qTBluA+aNeFnEUfkUFUEVgNYrdz7enn5W1n4Q9uBKYmFfJeSCcbsfziErsa4EU9Cz/pO0KE4WE1QdqRcvSXthQ==
The password supplied is f00b4r
The original string is The quick brown fox jumped over the lazy dog and ran away
Right, I have had to scrap the encryption algorithm on the Android side, which posed a challenge, to find one that is cross-platform compatible.
I have read up a lot about Rob Napier's RNCryptor, and after googling for an Android equivalent, in which I found JNCryptor , I took the plunge and employed RNCryptor on the iOS side.
Forked the JNCryptor code on github to add an enhancement in being able to specify custom settings, and to use SpongyCastle, for older versions of Android. From there on, both platforms were able to encrypt/decrypt interchangeably.
The reason I enhanced JNCryptor, was the iteration count for the PKDBF2 function was quite too high - 10,000 and was default value (since the code will be running on older handsets - it seized up - great if you have dual/quad core!), and needed to override the iteration count to be more "bearable" - 1,000. Using the custom setting was possible with RNCryptor.
Thanks to both Rob Napier and Duncan Jones for their work!

AES 128 Implementation with predefined key

I am trying to implement AES128 algorithm on Android, and I have referenced this link for a basic AES implementation (http://java.sun.com/developer/technicalArticles/Security/AES/AES_v1.html).
The problem is,for my project the key is predefined, and it is 36 bytes, not 16/24/32 bytes. So I always got a "key length not 128/194/256 bits" exception. I try the solution from iphone sdk(see this link: The iOS encryption framework) and it works even when I pass a 36 byte predefined key. As I can not find the implementation details for the BlockCipher.c/CommonCryptor.c released by Apple, Can any body help me figure out how they select 16 bytes from 36 bytes?
Thanks.
-----------------------------------update Sep 13th------------------------------------
In order to avoid confusion I provide some sample and my progress. I change some data that is confidential, but the length and format remain the same. And for saving time I only reveal the core functions. No comments for the code as I think the code is self-explained enough.
the iOS sample:
NSString * _key = #"some 36 byte key";
StringEncryption *crypto = [[[StringEncryption alloc] init] autorelease];
NSData *_inputData = [inputString dataUsingEncoding:NSUTF8StringEncoding];
CCOptions padding = kCCOptionPKCS7Padding;
NSData *encryptedData = [crypto encrypt:_inputData key:[_key dataUsingEncoding:NSUTF8StringEncoding] padding:&padding];
NSString *encryptedString = [encryptedData base64EncodingWithLineLength:0];
return encryptedString;
the [crypto encrypt] implementation is exactly the same as the link I mentioned above. It calls the doCipher in encryption mode. The core functions includes CCCryptorCreate, CCCryptorUpdate and CCCryptorFinal, which are from . The CCCryptorCreate deals with the key length. It passes the raw key bytes, and pass an integer 16 (kCCKeySizeAES128) as the key size and do the trick. The call hierarchy is like CCCryptorCreate/CommonCryptor.c => ccBlockCipherCallouts->CCBlockCipherInit/BlockCipher.c => ccAlgInfo->setkey/BlockCipher.c . setkey is actually a pointer to a function, for AES it points to aes_cc_set_key. And I can not find the aes_cc_set_key implementation, got lost here.
----------------------------------------Update Sep 13th -----------------------------
I change the _key in iOS sample code, manually taking the first 16 byte as the new key, other parts remain the same, and it is working!!! Up to this point I solve the key length problem.
But the Android version outputs different from the iOS version for some long plain text, like 30 or 40 bytes. my java implementation is like below:
String key = "some 16 byte key";
byte[] keyBytes = key.getBytes("UTF-8");
byte[] plainBytes = plainText.getBytes("UTF-8");
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(plainBytes);
String result = Base64.encodeBytes(encrypted);
return result;
Base64 is from org.apache.commons.codec.binary.Base64. What is the problem? or any hints on c/c++ libraries that can do the same thing? I can import it into android as well.
The remaining difference (provided that you only used the first 16 bytes of the key) is the cipher streaming mode. The iOS code uses CBC mode with an initialization set to all zeros. The Android code however uses ECB.
So the correct Java/Android code is:
// convert key to bytes
byte[] keyBytes = key.getBytes("UTF-8");
// Use the first 16 bytes (or even less if key is shorter)
byte[] keyBytes16 = new byte[16];
System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));
// convert plain text to bytes
byte[] plainBytes = plainText.getBytes("UTF-8");
// setup cipher
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16]; // initialization vector with all 0
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
// encrypt
byte[] encrypted = cipher.doFinal(plainBytes);
I have tested it with about 100 bytes of data and got exactly the same result on iOS and in Java.
There is no such thing as a 36-byte (288 bits) AES key. AES 256 would use a 32 byte key, so maybe that is what you have, with some additional header/trailer bytes. Where did you get this key from? What is the format? The Apple implementation may be throwing away the unneeded bytes, or it already knows about that special format you are using.
Is the 36 bytes actually a passphrase? If so, then it is likely that the key being used is SHA-256(passphrase) or SHA-512(passphrase).
ETA:
Re your update. I note that your code is using ECB mode. That is insecure. It may well be that Apple is using CBC mode, hence you difficulty in decrypting longer (more than 16 bytes) messages. Try changing the mode to CBC and using 16 more bytes of your mysterious input as the IV. Looking quickly at the Apple code for CommonCryptor.c, they appear to be using PKCS7 padding, so you should use that as well.
In case you want to apply base64 encoding for transporting over the network this is the right code:
public String encryptString(String string, String key)
{
byte[] aesData;
String base64="";
try
{
aesData = encrypt(key, string.getBytes("UTF8"));
base64 = Base64.encodeToString(aesData, Base64.DEFAULT);
}
catch (Exception e)
{
e.printStackTrace();
}
return base64;
}
public String decryptString(String string, String key)
{
byte[] debase64 = null;
String result="";
try
{
debase64=Base64.decode(string, Base64.DEFAULT);
byte[] aesDecrypted = decrypt(key, debase64);;
result = new String(aesDecrypted, "UTF8");
}
catch (Exception e)
{
e.printStackTrace();
}
return result;
}
private byte[] decrypt(String k, byte[] plainBytes) throws Exception
{
// convert key to bytes
byte[] keyBytes = k.getBytes("UTF-8");
// Use the first 16 bytes (or even less if key is shorter)
byte[] keyBytes16 = new byte[16];
System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));
// setup cipher
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16]; // initialization vector with all 0
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(iv));
// encrypt
byte[] encrypted = cipher.doFinal(plainBytes);
return encrypted;
}
private byte[] encrypt(String k, byte[] plainBytes) throws Exception
{
// convert key to bytes
byte[] keyBytes = k.getBytes("UTF-8");
// Use the first 16 bytes (or even less if key is shorter)
byte[] keyBytes16 = new byte[16];
System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));
// setup cipher
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16]; // initialization vector with all 0
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
// encrypt
byte[] encrypted = cipher.doFinal(plainBytes);
return encrypted;
}

Categories

Resources