AES Encryption in iOS and Java - android

I am a newbie to this encryption. I am creating an application for both android and iOS in which i have to encrypt(using AES Encryprtion) a file at server side and decrypt at client side in both iOS and Android App. I got code in internet to perform AES encryption and decryption for both Android and iOS, they are working fine. Server side they are using java. But the problem is Java Encrypted File cant be decrypted by iOS program, even I got the same filesize, But the file is not in correct format. I posted the code below...
Java Encryption and Decryption:
public static byte[] encrypt(byte[] data, byte[] key, byte[] ivs) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] finalIvs = new byte[16];
int len = ivs.length > 16 ? 16 : ivs.length;
System.arraycopy(ivs, 0, finalIvs, 0, len);
IvParameterSpec ivps = new IvParameterSpec(finalIvs);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static byte[] decrypt(byte[] data, byte[] key, byte[] ivs) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] finalIvs = new byte[16];
int len = ivs.length > 16 ? 16 : ivs.length;
System.arraycopy(ivs, 0, finalIvs, 0, len);
IvParameterSpec ivps = new IvParameterSpec(finalIvs);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
iOS Code for Decryption:
- (NSData *) decryptFile:(NSString *)key withData:(NSData *)fileData {
char keyPtr[kCCKeySizeAES128+1];
bzero(keyPtr, sizeof(keyPtr));
NSString* iv = #"12345678";
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [fileData length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,keyPtr, kCCKeySizeAES128,
iv /* initialization vector (optional) */,
[fileData bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesDecrypted);
if (cryptStatus == kCCSuccess) {
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
}
free(buffer); //free the buffer;
return nil;
}
Give me any solution or suggestion for this problem

The issue is with the iv parameter.
1) You are passing a NSString* as iv. You probably want to pass in the actual bytes.
2) The length of iv should be 16 (in this case) as per the api docs of CCCrypt. See link below:
http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-36064/CommonCrypto/CommonCryptor.h

Related

Swift AES Encryption throws error while in Android doesn't

I tried to do AES encryption in Swift which I do in Android like this:
public static String Encrypt(String text, String key) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] keyBytes = new byte[16];
byte[] b = key.getBytes("UTF-8");
int len = b.length;
if (len > keyBytes.length)
len = keyBytes.length;
System.arraycopy(b, 0, keyBytes, 0, len);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(keyBytes);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] results = cipher.doFinal(text.getBytes("UTF-8"));
return Base64.encodeToString(results, Base64.DEFAULT);
}
catch (Exception ex){return "error"+ex.getMessage();}
}
Below is the equivalent code in Swift 3.2:
func aesEncrypt(key: String, iv: String) throws -> String{
let data = self.data(using: String.Encoding.utf8)
let enc = try AES(key: key.bytes, blockMode: BlockMode.CBC(iv: iv.bytes, padding: Padding.pkcs5).encrypt(data!.bytes)
let encData = NSData(bytes: enc, length: Int(enc.count))
let base64String: String = encData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0));
let result = String(base64String)
return result!}
In Android it doesn't matter for key: I can fill with any string (no length limitations). But when using Swift I have to use a 32 charachter string for key and a 16 charachter string for IV, otherwise it will throw an error.
Here is the Swift usage:
let data = "this is string which I want to be encrypted"
let key = "bbbb98232-a343-4343f-2111"
let iv = "0000000000000000" // lenght = 16 like android code
let encryptedString = data.aesEncrypt(key: key, iv: iv);
Is there maybe some mistake in my Swift code?
You can try the below Swift code for AES encryption. Its String extension.
import Foundation
import CommonCrypto
extension String {
func aesEncrypt(key: String, initializationVector: String, options: Int = kCCOptionPKCS7Padding) -> String? {
if let keyData = key.data(using: String.Encoding.utf8),
let data = self.data(using: String.Encoding.utf8),
let cryptData = NSMutableData(length: Int((data.count)) + kCCBlockSizeAES128) {
let keyLength = size_t(kCCKeySizeAES128)
let operation: CCOperation = UInt32(kCCEncrypt)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
let options: CCOptions = UInt32(options)
var numBytesEncrypted: size_t = 0
let cryptStatus = CCCrypt(operation,
algoritm,
options,
(keyData as NSData).bytes, keyLength,
initializationVector,
(data as NSData).bytes, data.count,
cryptData.mutableBytes, cryptData.length,
&numBytesEncrypted)
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.length = Int(numBytesEncrypted)
let base64cryptString = cryptData.base64EncodedString(options: .lineLength64Characters)
return base64cryptString
} else {
return nil
}
}
return nil
}
}

RuntimeException when closing CipherInputStream

I kind of got stuck with this exception:
java.lang.RuntimeException: error:0407806d:RSA routines:decrypt:DATA_LEN_NOT_EQUAL_TO_MOD_LEN
at com.android.org.conscrypt.NativeCrypto.RSA_private_decrypt(Native Method)
at com.android.org.conscrypt.OpenSSLCipherRSA.engineDoFinal(OpenSSLCipherRSA.java:274)
at javax.crypto.Cipher.doFinal(Cipher.java:1440)
at javax.crypto.CipherInputStream.close(CipherInputStream.java:190)
...
It is thrown when I close the CipherInputStream on Android Marshmallow. Everything seems to work with earlier Android Versions.
What does DATA_LEN_NOT_EQUAL_TO_MOD_LEN mean? Why does it seem to decrypt (call to RSA_private_decrypt) when it should free resource handles (close)?
UPDATE:
I managed to reproduce the error with some test code. It encrypts and decrypts "foobar". One time using the cipher directly and one time through a CipherInputStream (like it's done in the original app).
Everything works on android < 6 and the non-streaming code is even working on android 6.
I was able to get the streaming code to work on android 6 when I changed the explicit cipher RSA/ECB/PKCS1Padding to generic RSA.
But I would bet that it's there for a reason ;)
static final String RSA_ALGO = "RSA/ECB/PKCS1Padding";
// static final String RSA_ALGO = "RSA";
private void _testCrypto2() throws Exception {
KeyPairGenerator keyGen;
KeyPair keys;
byte[] encrypted;
byte[] decrypted;
String input;
String output;
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
keys = keyGen.generateKeyPair();
input = "foobar";
// Plain crypto.
encrypted = this.RSAEncrypt(input, keys.getPublic());
output = this.RSADecrypt(encrypted, keys.getPrivate());
// Streaming crypto.
encrypted = this.RSAEncryptStream(input, keys.getPublic());
output = this.RSADecryptStream(encrypted, keys.getPrivate());
}
public byte[] RSAEncrypt(final String plain, PublicKey _publicKey) throws Exception {
byte[] encryptedBytes;
Cipher cipher;
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, _publicKey);
encryptedBytes = cipher.doFinal(plain.getBytes());
return encryptedBytes;
}
public String RSADecrypt(final byte[] encryptedBytes, PrivateKey _privateKey) throws Exception {
Cipher cipher;
byte[] decryptedBytes;
String decrypted;
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.DECRYPT_MODE, _privateKey);
decryptedBytes = cipher.doFinal(encryptedBytes);
decrypted = new String(decryptedBytes);
return decrypted;
}
public byte[] RSAEncryptStream(final String _plain, PublicKey _publicKey) throws Exception {
Cipher cipher;
InputStream in;
ByteArrayOutputStream out;
int numBytes;
byte buffer[] = new byte[0xffff];
in = new ByteArrayInputStream(_plain.getBytes());
out = new ByteArrayOutputStream();
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, _publicKey);
try {
in = new CipherInputStream(in, cipher);
while ((numBytes = in.read(buffer)) != -1) {
out.write(buffer, 0, numBytes);
}
}
finally {
in.close();
}
return out.toByteArray();
}
public String RSADecryptStream(final byte[] _encryptedBytes, PrivateKey _privateKey) throws Exception {
Cipher cipher;
InputStream in;
ByteArrayOutputStream out;
int numBytes;
byte buffer[] = new byte[0xffff];
in = new ByteArrayInputStream(_encryptedBytes);
out = new ByteArrayOutputStream();
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.DECRYPT_MODE, _privateKey);
try {
in = new CipherInputStream(in, cipher);
while ((numBytes = in.read(buffer)) != -1) {
out.write(buffer, 0, numBytes);
}
}
finally {
in.close();
}
return new String(out.toByteArray());
}
However, it looks like there are two directions for a fix:
Getting rid of the streaming for RSA
Removing explicit RSA cipher instantiation
What do you think?
It looks like there were some changes for the default security providers of android.
Cipher c;
Provider p;
StringBuilder bldr;
c = Cipher.getInstance("RSA");
p = cipher.getProvider();
bldr = new StringBuilder();
bldr.append(_p.getName())
.append(" ").append(_p.getVersion())
.append(" (").append(_p.getInfo()).append(")");
Log.i("test", bldr.toString());
It seems to use a version of BouncyCastle on all tested Android versions (I tested down to 2.3):
Android 5:
BC 1.5 (BouncyCastle Security Provider v1.50)
Android 6:
BC 1.52 (BouncyCastle Security Provider v1.52)
However, something changed with the "explicit" cipher:
c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
Android 4.1.2:
BC 1.46 (BouncyCastle Security Provider v1.46)
Android 4.4.2:
AndroidOpenSSL 1.0 (Android's OpenSSL-backed security provider)
Android 5.1.1:
AndroidOpenSSL 1.0 (Android's OpenSSL-backed security provider)
Android 6.0.1:
AndroidKeyStoreBCWorkaround 1.0 (Android KeyStore security provider to work around Bouncy Castle)
So the final solution is setting the provider explicitly to BouncyCastle which is working on all tested android versions, even with streaming:
Provider p;
Cipher c;
p = Security.getProvider("BC");
c = Cipher.getInstance("RSA/ECB/PKCS1Padding", p);

Matching iOS & Android AES - bad padding

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.

Pad Block Corrupted

I have to do the following operation:
Encryption VB -> Decryption VB
Encryption Android - Decryption Android
Encryption VB -> Decryption Android
Encryption Android -> decryption VB
So far I succedded do encrypt and decrypt on Android.
When I encrypt in VB and try to decrypt on Android, I get the following exception:
E/Exception: pad block corrupted
I also have to mention that when i encrypt short strings in VB and decrypt them also in VB, everything works well. But when i encrypt a larger array of bytes, the decryption works but the result is not the one expected.
Can somebody give me a hint of how to solve the problem?
Thank you !
Here is my code:
.NET functions
Public Function AES_Encrypt2(ByVal byteArray() As Byte, ByVal key As String, Optional ByVal ShortKey As Boolean = False) As String
Try
Dim FirstKeyBytes() As Byte = Encoding.UTF8.GetBytes(key)
If Not FirstKeyBytes Is Nothing Then
If FirstKeyBytes.Length < 32 Then
Array.Resize(FirstKeyBytes, 32)
End If
End If
Dim KeyBytes() As Byte
If ShortKey Then
KeyBytes = New Byte(15) {}
Array.Copy(FirstKeyBytes, KeyBytes, 16)
Else
KeyBytes = New Byte(31) {}
Array.Copy(FirstKeyBytes, KeyBytes, 32)
End If
Dim InitialVectorBytes() As Byte = New Byte() {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 'Encoding.UTF8.GetBytes("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")
Dim SymmetricKey As New RijndaelManaged()
SymmetricKey.Mode = CipherMode.CBC
SymmetricKey.Padding = PaddingMode.PKCS7
Dim Encryptor As ICryptoTransform = SymmetricKey.CreateEncryptor(KeyBytes, InitialVectorBytes)
Dim MemStream As New MemoryStream()
Dim CryptoStream As New CryptoStream(MemStream, Encryptor, CryptoStreamMode.Write)
CryptoStream.Write(byteArray, 0, byteArray.Length)
CryptoStream.FlushFinalBlock()
MemStream.Close()
CryptoStream.Close()
Dim CipherTextBytes As Byte() = MemStream.ToArray()
Dim encryptedString As String = Convert.ToBase64String(CipherTextBytes)
Return encryptedString
Catch ex As Exception
Return String.Empty
End Try
End Function
Public Function AES_Decrypt2(ByVal encryptedString As String, ByVal key As String, Optional ByVal ShortKey As Boolean = False) As String
Try
Dim PlainTextBytes1 As Byte() = Convert.FromBase64String(encryptedString)
Dim FirstKeyBytes() As Byte = Encoding.UTF8.GetBytes(key)
If Not FirstKeyBytes Is Nothing Then
If FirstKeyBytes.Length < 32 Then
Array.Resize(FirstKeyBytes, 32)
End If
End If
Dim KeyBytes() As Byte
If ShortKey Then
KeyBytes = New Byte(15) {}
Array.Copy(FirstKeyBytes, KeyBytes, 16)
Else
KeyBytes = New Byte(31) {}
Array.Copy(FirstKeyBytes, KeyBytes, 32)
End If
Dim SymmetricKey As New RijndaelManaged()
Dim InitialVectorBytes As Byte() = New Byte() {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 'Encoding.UTF8.GetBytes("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")
SymmetricKey.Mode = CipherMode.CBC
SymmetricKey.Padding = PaddingMode.PKCS7
Dim Decryptor As ICryptoTransform = SymmetricKey.CreateDecryptor(KeyBytes, InitialVectorBytes)
Dim MemStream1 As New MemoryStream(PlainTextBytes1)
Dim CryptoStream As New CryptoStream(MemStream1, Decryptor, CryptoStreamMode.Read)
Dim pltxt As Byte() = New Byte(PlainTextBytes1.Length - 1) {}
Dim d As Integer = CryptoStream.Read(pltxt, 0, pltxt.Length)
MemStream1.Close()
CryptoStream.Close()
Dim textConverter As New ASCIIEncoding()
Dim round As String = textConverter.GetString(pltxt, 0, d)
Return round
Catch ex As Exception
Return String.Empty
End Try
End Function
And Android methods:
public static String encrypt(byte[] input, String key) {
try {
byte[] iv = new byte[16];
AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
String newKey = "";
if (key.length() >= 32) {
newKey = key.substring(0, 32);
} else {
for (int i = key.length(); i < 32; i++) {
key += "0";
}
newKey = key.substring(0, 32);
}
SecretKeySpec skeySpec = new SecretKeySpec(newKey.getBytes(), "AES");
//skeySpec = new SecretKeySpec(newKey.getBytes(), 0, newKey.length(), "AES");
Cipher fileCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
fileCipher.init(1, skeySpec, paramSpec);
byte[] decrypted = fileCipher.doFinal(input);
byte[] base64enc = Base64.encode(decrypted, 0);
return new String(base64enc);
} catch (Exception e) {
Log.e("Exception", e.getMessage());
}
return null;
}
public static byte[] decrypt(String input, String key) {
try {
byte[] iv = new byte[16];
AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
byte[] base64enc = Base64.decode(input.getBytes(), 0);
String newKey = "";
if (key.length() >= 32) {
newKey = key.substring(0, 32);
} else {
for (int i = key.length(); i < 32; i++) {
key += "0";
}
newKey = key.substring(0, 32);;
}
SecretKeySpec skeySpec = new SecretKeySpec(newKey.getBytes(), "AES");
Cipher fileCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
fileCipher.init(2, skeySpec, paramSpec);
int x = base64enc.length;
return fileCipher.doFinal(base64enc);
} catch (Exception e) {
Log.e("Exception: ", e.getMessage());
}
return null;
}
I guess the main issue is that the key generation is different in both pieces of code. Passwords are not keys, you should use either binary, randomly generated keys or a good key derivation mechanism like PBKDF2.
Trying to find a well vetted lib. that does use the same protocol for encryption in .NET and Java (/Android) would also be a good idea.
In general, input to cryptographic algorithms need to be binary. Always compare all inputs of your algorithms using hexadecimal encoding right before executing the algorithm.

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