AndroidとIPhoneプラットフォームの両方でAESアルゴリズムを使用してテキストを暗号化しようとしています。同じ暗号化/復号化アルゴリズム(AES-128)と同じ固定変数(キー、IV、モード)、両方のプラットフォームで異なる結果が得られます。暗号化/復号化のテストに使用している両方のプラットフォームからのコードサンプルを含めています。何が間違っているのかを判断するのに役立つ情報をいただければ幸いです。
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));
}
}
iPhone(Objective-C)コード:
- (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];
}
私の参考文献のいくつか:
IPhoneの場合は AESCrypt-ObjC を使用し、Androidの場合は次のコードを使用します:
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;
}
}
あなたが異なる結果を得るのも不思議ではありません。
問題は、鍵の導出にSHA1PRNGを誤用することです。 AFAIKSHA1PRNGが内部でどのように機能するかについての共通の標準はありません。 AFAIRでさえ、J2SEとBouncycasteの実装でさえ、同じシードを使用して異なる結果を出力します。
したがって、getRawKey(byte[] seed)
を実装すると、ランダムなキーが生成されます。暗号化にキーを使用すると、そのキーに依存する結果が得られます。キーはランダムであるため、iOSで同じキーを取得することはできません。したがって、異なる結果が得られます。
鍵導出関数が必要な場合は、鍵導出に関してほぼ完全に標準化されているPBKDF2のような関数を使用してください。
Androidでは、getBytes()
を使用しています。既知の文字セットではなくデフォルトの文字セットを使用していることを意味するため、これはエラーです。代わりにgetBytes("UTF-8")
を使用して、取得するバイトを正確に把握してください。
Objective-Cに相当するものはわかりませんが、デフォルトに依存しません。文字列をバイトに変換するときは、UTF-8を明示的に指定してください。そうすれば、両側で同じバイトを取得できます。
また、Objective-CコードではMD5を使用していますが、Androidコードでは使用していません。これは意図的なものですか?
パスワードベースのAES暗号化 については、私の答えを参照してください。これは、「シード」をパスワードとして効果的に使用しているためです。 (必要に応じて、キーの長さを256から128に変更するだけです。)
同じ値でDRBGをシードして同じキーを生成しようとすることは、信頼できません。
次に、Android暗号化でCBCまたはIVを使用していません。私の例では、それを適切に行う方法も示しています。ちなみに、暗号化するメッセージごとに新しいIVを生成する必要があります。 、私の例が示すように、暗号文と一緒に送信します。それ以外の場合は、CBCを使用しても意味がありません。
AndroidとiPhoneの互換性のあるコードの例が必要な場合は、iOSの場合は RNCryptorライブラリ 、Java /の場合は JNCryptorライブラリ をご覧ください。アンドロイド。
どちらのプロジェクトもオープンソースであり、共通のデータ形式を共有しています。これらのライブラリでは、AES 256ビットが使用されますが、必要に応じてコードを適応させて128ビットAESをサポートするのは簡単です。
受け入れられた答えによると、両方のライブラリはPBKDF2を使用しています。