J'essaie de chiffrer du texte à l'aide de l'algorithme AES sur les plateformes Android et iPhone. Mon problème est que, même en utilisant le même algorithme de chiffrement/déchiffrement (AES-128) et les mêmes variables fixes (clé, IV, mode), le résultat est différent sur les deux plates-formes. J'inclus des échantillons de code des deux plates-formes, que j'utilise pour tester le cryptage/décryptage. J'apprécierais une aide pour déterminer ce que je fais mal.
Code Android:
public class Crypto {
private final static String HEX = "0123456789ABCDEF";
public static String encrypt(String seed, String cleartext)
throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] result = encrypt(rawKey, cleartext.getBytes());
return toHex(result);
}
public static String decrypt(String seed, String encrypted)
throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] enc = toByte(encrypted);
byte[] result = decrypt(rawKey, enc);
return new String(result);
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("CBC");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(seed);
kgen.init(128, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
private static byte[] decrypt(byte[] raw, byte[] encrypted)
throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
public static String toHex(String txt) {
return toHex(txt.getBytes());
}
public static String fromHex(String hex) {
return new String(toByte(hex));
}
public static byte[] toByte(String hexString) {
int len = hexString.length() / 2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2),
16).byteValue();
return result;
}
public static String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2 * buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}
}
Code IPhone (Objective-C) Code:
- (NSData *) transform:(CCOperation) encryptOrDecrypt data:(NSData *) inputData {
NSData* secretKey = [Cipher md5:cipherKey];
CCCryptorRef cryptor = NULL;
CCCryptorStatus status = kCCSuccess;
uint8_t iv[kCCBlockSizeAES128];
memset((void *) iv, 0x0, (size_t) sizeof(iv));
status = CCCryptorCreate(encryptOrDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
[secretKey bytes], kCCKeySizeAES128, iv, &cryptor);
if (status != kCCSuccess) {
return nil;
}
size_t bufsize = CCCryptorGetOutputLength(cryptor, (size_t)[inputData length], true);
void * buf = malloc(bufsize * sizeof(uint8_t));
memset(buf, 0x0, bufsize);
size_t bufused = 0;
size_t bytesTotal = 0;
status = CCCryptorUpdate(cryptor, [inputData bytes], (size_t)[inputData length],
buf, bufsize, &bufused);
if (status != kCCSuccess) {
free(buf);
CCCryptorRelease(cryptor);
return nil;
}
bytesTotal += bufused;
status = CCCryptorFinal(cryptor, buf + bufused, bufsize - bufused, &bufused);
if (status != kCCSuccess) {
free(buf);
CCCryptorRelease(cryptor);
return nil;
}
bytesTotal += bufused;
CCCryptorRelease(cryptor);
return [NSData dataWithBytesNoCopy:buf length:bytesTotal];
}
+ (NSData *) md5:(NSString *) stringToHash {
const char *src = [stringToHash UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(src, strlen(src), result);
return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
}
Quelques unes de mes références:
Pour iPhone, j'ai utilisé AESCrypt-ObjC , et pour Android, utilisez ce code:
public class AESCrypt {
private final Cipher cipher;
private final SecretKeySpec key;
private AlgorithmParameterSpec spec;
public AESCrypt(String password) throws Exception
{
// hash password with SHA-256 and crop the output to 128-bit for key
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(password.getBytes("UTF-8"));
byte[] keyBytes = new byte[32];
System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);
cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
key = new SecretKeySpec(keyBytes, "AES");
spec = getIV();
}
public AlgorithmParameterSpec getIV()
{
byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
IvParameterSpec ivParameterSpec;
ivParameterSpec = new IvParameterSpec(iv);
return ivParameterSpec;
}
public String encrypt(String plainText) throws Exception
{
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
String encryptedText = new String(Base64.encode(encrypted, Base64.DEFAULT), "UTF-8");
return encryptedText;
}
public String decrypt(String cryptedText) throws Exception
{
cipher.init(Cipher.DECRYPT_MODE, key, spec);
byte[] bytes = Base64.decode(cryptedText, Base64.DEFAULT);
byte[] decrypted = cipher.doFinal(bytes);
String decryptedText = new String(decrypted, "UTF-8");
return decryptedText;
}
}
Cela ne m'étonne pas que vous obteniez des résultats différents.
Votre problème est que vous utilisez à mauvais escient un SHA1PRNG pour la dérivation de clé. Autant que je sache, il n’existe pas de norme commune sur le fonctionnement interne d’un SHA1PRNG. AFAIR même l’implémentation J2SE et Bouncycaste produit des résultats différents en utilisant la même graine.
Par conséquent, votre implémentation de votre getRawKey(byte[] seed)
vous générera une clé aléatoire. Si vous utilisez la clé pour le chiffrement, vous obtenez un résultat qui dépend de cette clé. Comme la clé est aléatoire, vous n'obtiendrez pas la même clé sur iOS et vous obtenez donc un résultat différent.
Si vous souhaitez une fonction de dérivation de clé, utilisez une fonction telle que PBKDF2 avec une définition presque entièrement normalisée en ce qui concerne la dérivation de clé.
Sur Android, vous utilisez getBytes()
. C'est une erreur car cela signifie que vous utilisez le jeu de caractères par défaut plutôt qu'un jeu de caractères connu. Utilisez plutôt getBytes("UTF-8")
pour savoir exactement quels octets vous allez obtenir.
Je ne connais pas l'équivalent d'Objective-C, mais ne vous fiez pas à la valeur par défaut. Spécifiez explicitement UTF-8 lors de la conversion de chaînes en octets. De cette façon, vous obtiendrez les mêmes octets des deux côtés.
Je note également que vous utilisez MD5 dans le code Objective-C mais pas dans le code Android. Est-ce délibéré?
Voir ma réponse pour le cryptage AES basé sur un mot de passe, puisque vous utilisez efficacement votre "graine" comme mot de passe. (Il suffit de changer la longueur de la clé de 256 à 128, si c'est ce que vous voulez.)
Essayer de générer la même clé en générant un DRBG avec la même valeur n’est pas fiable.
Ensuite, vous n’utilisez pas CBC ni IV dans votre cryptage Android. Mon exemple montre comment le faire correctement. En passant, vous devez générer un nouvel IV pour chaque message que vous chiffrez, comme le montre mon exemple, et l'envoyer avec le texte chiffré. Sinon, il n'y a aucun intérêt à utiliser CBC.
Si vous voulez un exemple de code compatible pour Android et iPhone, consultez les bibliothèques RNCryptor pour iOS et JNCryptor pour Java/Android.
Les deux projets sont open source et partagent un format de données commun. Dans ces bibliothèques, AES 256 bits est utilisé, mais il serait trivial d'adapter le code si nécessaire pour prendre en charge l'AES 128 bits.
Selon la réponse acceptée, les deux bibliothèques utilisent PBKDF2.