36

まず第一に、 Android 4.2 が私の AES 暗号化/復号化コード暗号化エラーを Android 4.2 と提供されたソリューションで破ったことを既に見てきました:

SecureRandom sr = null;
if (android.os.Build.VERSION.SDK_INT >= JELLY_BEAN_4_2) {
    sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
} else {
    sr = SecureRandom.getInstance("SHA1PRNG");
}

Android 4.2でAndroid <4.2で暗号化されたデータをデコードすると、次のようになるため、私には機能しません。

javax.crypto.BadPaddingException: pad block corrupted
at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:709)

私のコードは非常に単純で、Android 4.2 までは機能していました。

public static byte[] encrypt(byte[] data, String seed) throws Exception {

    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
    secrand.setSeed(seed.getBytes());
    keygen.init(128, secrand);

    SecretKey seckey = keygen.generateKey();
    byte[] rawKey = seckey.getEncoded();

    SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
    return cipher.doFinal(data);
}

public static byte[] decrypt(byte[] data, String seed) throws Exception {

    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
    secrand.setSeed(seed.getBytes());
    keygen.init(128, secrand);

    SecretKey seckey = keygen.generateKey();
    byte[] rawKey = seckey.getEncoded();

    SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, skeySpec);
    return cipher.doFinal(data);
}

私の推測では、Android 4.2 で変更されたのはデフォルト プロバイダーだけではありません。それ以外の場合、私のコードは提案されたソリューションで動作します。

私のコードは、ずっと前に StackOverflow で見つけた投稿に基づいていました。バイト配列を暗号化および復号化するだけなので、言及された投稿とは異なることがわかりますが、他のソリューションは文字列を暗号化および復号化します(HEX文字列だと思います)。

それは種と関係がありますか?最小/最大長、文字の制限などはありますか?

アイデア/解決策はありますか?

編集:多くのテストの後、2つの問題があることがわかりました:

  1. プロバイダーは Android 4.2 (API 17) で変更されました -> これは簡単に修正できます。投稿の冒頭で述べた解決策を適用するだけです

  2. BouncyCastle は Android 2.2 (API 8) -> Android2.3 (API 9) で 1.34 から 1.45 に変更されたため、以前に説明した復号化の問題は、ここで説明したものと同じです: BouncyCastle 1.45 にアップグレードすると AES エラーが発生する

質問は、BouncyCastle 1.34 で暗号化されたデータを BouncyCastle 1.45+ で復元する方法はありますか?

4

7 に答える 7

46

最初の免責事項:

SecureRandomキーを導出するために使用しないでください。これは壊れていて意味がありません。

質問の次のコード ブロックは、パスワードから決定論的にキーを導出しようとします。これは、パスワードが乱数ジェネレータの「シード」に使用されるため、「シード」と呼ばれます。

KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
secrand.setSeed(seed.getBytes());
keygen.init(128, secrand);
SecretKey seckey = keygen.generateKey();

ただし、"SHA1PRNG"アルゴリズムは明確に定義されておらず、 の実装では、結果として"SHA1PRNG"異なるキーまたは完全にランダムなキーが返される場合があります。


ディスクから AES キーを読み取る場合は、実際のキーを保存するだけで、この奇妙なダンスを経験しないでください。SecretKey次のようにして、バイトから AES の使用法を取得できます。

    SecretKey key = new SecretKeySpec(keyBytes, "AES");

パスワードを使用してキーを導出する場合は、 Nelenkov の優れたチュートリアルに従ってください。経験則として、salt のサイズはキーの出力と同じサイズにする必要があります。

(iterationCount作業係数) はもちろん変更される可能性があり、CPU パワーの進歩に応じて変更する必要があります。一般に、2018 年の時点で 40 ~ 100K を下回らないようにすることをお勧めします。本当に脆弱なパスワードの代わりにはなりません。

次のようになります。

    /* User types in their password: */
    String password = "password";

    /* Store these things on disk used to derive key later: */
    int iterationCount = 1000;
    int saltLength = 32; // bytes; should be the same size as the output (256 / 8 = 32)
    int keyLength = 256; // 256-bits for AES-256, 128-bits for AES-128, etc
    byte[] salt; // Should be of saltLength

    /* When first creating the key, obtain a salt with this: */
    SecureRandom random = new SecureRandom();
    byte[] salt = new byte[saltLength];
    random.nextBytes(salt);

    /* Use this to derive the key from the password: */
    KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
                iterationCount, keyLength);
    SecretKeyFactory keyFactory = SecretKeyFactory
                .getInstance("PBKDF2WithHmacSHA1");
    byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
    SecretKey key = new SecretKeySpec(keyBytes, "AES");

それでおしまい。その他、使用してはならないもの。

于 2012-11-18T08:26:19.230 に答える
11

問題は、新しいプロバイダでは、次のコード スニペットが

KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
secrand.setSeed(seed.getBytes());
keygen.init(128, secrand);
SecretKey seckey = keygen.generateKey();
byte[] rawKey = seckey.getEncoded();

rawKey実行されるたびに異なる真のランダムを生成します。したがって、データの暗号化に使用されたキーとは異なるキーで復号化しようとすると、例外が発生します。この方法で生成されたキーまたはデータを復元することはできず、シードのみが保存されています。

于 2012-12-07T14:30:45.667 に答える
4

私のためにそれを修正したのは(@Giorgioが示唆したように)これを置き換えるだけでした:

SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");

これで

SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG", "Crypto");
于 2014-02-06T18:45:33.293 に答える
0

これらすべてが、すべての Android デバイス (>=2.1) で決定論的な暗号化パスワードを生成するのに役立たなかったため、別の AES 実装を探しました。すべてのデバイスで機能するものを見つけました。私はセキュリティの専門家ではないため、技術が可能な限り安全でない場合は、私の回答に反対票を投じないでください。以前に直面したのと同じ問題に遭遇した人のためにコードを投稿しているだけです。

import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import android.util.Log;

public class EncodeDecodeAES {


    private static final String TAG_DEBUG = "TAG";
    private IvParameterSpec ivspec;
    private SecretKeySpec keyspec;
    private Cipher cipher;

    private String iv = "fedcba9876543210";//Dummy iv (CHANGE IT!)
    private String SecretKey = "0123456789abcdef";//Dummy secretKey (CHANGE IT!)

    public EncodeDecodeAES() {
        ivspec = new IvParameterSpec(iv.getBytes());

        keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");

        try {
            cipher = Cipher.getInstance("AES/CBC/NoPadding");
        } catch (GeneralSecurityException e) {
            Log.d(TAG_DEBUG, e.getMessage());
        }
    }

    public byte[] encrypt(String text) throws Exception {
        if (text == null || text.length() == 0)
            throw new Exception("Empty string");

        byte[] encrypted = null;

        try {
            cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);

            encrypted = cipher.doFinal(padString(text).getBytes());
        } catch (Exception e) {
            Log.d(TAG_DEBUG, e.getMessage());
            throw new Exception("[encrypt] " + e.getMessage());
        }

        return encrypted;
    }

    public byte[] decrypt(String code) throws Exception {
        if (code == null || code.length() == 0)
            throw new Exception("Empty string");

        byte[] decrypted = null;

        try {
            cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);

            decrypted = cipher.doFinal(hexToBytes(code));
        } catch (Exception e) {
            Log.d(TAG_DEBUG, e.getMessage());
            throw new Exception("[decrypt] " + e.getMessage());
        }
        return decrypted;
    }

    public static String bytesToHex(byte[] data) {
        if (data == null) {
            return null;
        }

        int len = data.length;
        String str = "";
        for (int i = 0; i < len; i++) {
            if ((data[i] & 0xFF) < 16)
                str = str + "0" + java.lang.Integer.toHexString(data[i] & 0xFF);
            else
                str = str + java.lang.Integer.toHexString(data[i] & 0xFF);
        }
        return str;
    }

    public static byte[] hexToBytes(String str) {
        if (str == null) {
            return null;
        } else if (str.length() < 2) {
            return null;
        } else {
            int len = str.length() / 2;
            byte[] buffer = new byte[len];
            for (int i = 0; i < len; i++) {
                buffer[i] = (byte) Integer.parseInt(str.substring(i * 2, i * 2 + 2), 16);
            }
            return buffer;
        }
    }

    private static String padString(String source) {
        char paddingChar = ' ';
        int size = 16;
        int x = source.length() % size;
        int padLength = size - x;

        for (int i = 0; i < padLength; i++) {
            source += paddingChar;
        }

        return source;
    }
}

次のように使用できます。

EncodeDecodeAES aes = new EncodeDecodeAES ();
/* Encrypt */
String encrypted = EncodeDecodeAES.bytesToHex(aes.encrypt("Text to Encrypt"));
/* Decrypt */
String decrypted = new String(aes.decrypt(encrypted));

出典:こちら

于 2013-08-21T13:04:17.540 に答える
0

あなたの質問にお答えすることはできませんが、私は単にこれを回避しようとします >-デバイス/OS バージョン間で bouncycastle に問題が発生した場合は、組み込みバージョンを完全に破棄し、代わりに bouncycastle を jar として追加する必要がありますプロジェクトに変更importし、そのjarを指すように変更し、再構築して、すべてが機能すると仮定すると、Androidの組み込みバージョンの変更の影響を受けなくなります。

于 2012-11-17T18:39:59.553 に答える