31

次のコードは、最新の 4.2 を除くすべてのバージョンの Android で動作しています。

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * Util class to perform encryption/decryption over strings. <br/>
 */
public final class UtilsEncryption
{
    /** The logging TAG */
    private static final String TAG = UtilsEncryption.class.getName();

    /** */
    private static final String KEY = "some_encryption_key";

    /**
     * Avoid instantiation. <br/>
     */
    private UtilsEncryption()
    {
    }

    /** The HEX characters */
    private final static String HEX = "0123456789ABCDEF";

    /**
     * Encrypt a given string. <br/>
     * 
     * @param the string to encrypt
     * @return the encrypted string in HEX
     */
    public static String encrypt( String cleartext )
    {
        try
        {
            byte[] result = process( Cipher.ENCRYPT_MODE, cleartext.getBytes() );
            return toHex( result );
        }
        catch ( Exception e )
        {
            System.out.println( TAG + ":encrypt:" + e.getMessage() );
        }
        return null;
    }

    /**
     * Decrypt a HEX encrypted string. <br/>
     * 
     * @param the HEX string to decrypt
     * @return the decrypted string
     */
    public static String decrypt( String encrypted )
    {
        try
        {
            byte[] enc = fromHex( encrypted );
            byte[] result = process( Cipher.DECRYPT_MODE, enc );
            return new String( result );
        }
        catch ( Exception e )
        {
            System.out.println( TAG + ":decrypt:" + e.getMessage() );
        }
        return null;
    }


    /**
     * Get the raw encryption key. <br/>
     * 
     * @param the seed key
     * @return the raw key
     * @throws NoSuchAlgorithmException
     */
    private static byte[] getRawKey()
        throws NoSuchAlgorithmException
    {
        KeyGenerator kgen = KeyGenerator.getInstance( "AES" );
        SecureRandom sr = SecureRandom.getInstance( "SHA1PRNG" );
        sr.setSeed( KEY.getBytes() );
        kgen.init( 128, sr );
        SecretKey skey = kgen.generateKey();
        return skey.getEncoded();
    }

    /**
     * Process the given input with the provided mode. <br/>
     * 
     * @param the cipher mode
     * @param the value to process
     * @return the processed value as byte[]
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     */
    private static byte[] process( int mode, byte[] value )
        throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,     NoSuchAlgorithmException,
        NoSuchPaddingException
    {
        SecretKeySpec skeySpec = new SecretKeySpec( getRawKey(), "AES" );
        Cipher cipher = Cipher.getInstance( "AES" );
        cipher.init( mode, skeySpec );
        byte[] encrypted = cipher.doFinal( value );
        return encrypted;
    }

    /**
     * Decode an HEX encoded string into a byte[]. <br/>
     * 
     * @param the HEX string value
     * @return the decoded byte[]
     */
    protected static byte[] fromHex( String value )
    {
        int len = value.length() / 2;
        byte[] result = new byte[len];
        for ( int i = 0; i < len; i++ )
        {
            result[i] = Integer.valueOf( value.substring( 2 * i, 2 * i + 2 ), 16     ).byteValue();
        }
        return result;
    }

    /**
     * Encode a byte[] into an HEX string. <br/>
     * 
     * @param the byte[] value
     * @return the HEX encoded string
     */
    protected static String toHex( byte[] value )
    {
        if ( value == null )
        {
            return "";
        }
        StringBuffer result = new StringBuffer( 2 * value.length );
        for ( int i = 0; i < value.length; i++ )
        {
            byte b = value[i];

            result.append( HEX.charAt( ( b >> 4 ) & 0x0f ) );
            result.append( HEX.charAt( b & 0x0f ) );
        }
        return result.toString();
    }
}

これは、エラーを再現するために作成した小さな単体テストです

import junit.framework.TestCase;

public class UtilsEncryptionTest
    extends TestCase
{
    /** A random string */
    private static String ORIGINAL = "some string to test";

    /**
     * The HEX value corresponds to ORIGINAL. <br/>
     * If you change ORIGINAL, calculate the new value on one of this sites:
     * <ul>
     * <li>http://www.string-functions.com/string-hex.aspx</li>
     * <li>http://www.yellowpipe.com/yis/tools/encrypter/index.php</li>
     * <li>http://www.convertstring.com/EncodeDecode/HexEncode</li>
     * </ul>
     */
    private static String HEX = "736F6D6520737472696E6720746F2074657374";

    public void testToHex()
    {
         String hexString = UtilsEncryption.toHex( ORIGINAL.getBytes() );

         assertNotNull( "The HEX string should not be null", hexString );
         assertTrue( "The HEX string should not be empty", hexString.length() > 0 );
         assertEquals( "The HEX string was not encoded correctly", HEX, hexString );
    }

    public void testFromHex()
    {
         byte[] stringBytes = UtilsEncryption.fromHex( HEX );

         assertNotNull( "The HEX string should not be null", stringBytes );
        assertTrue( "The HEX string should not be empty", stringBytes.length > 0 );
        assertEquals( "The HEX string was not encoded correctly", ORIGINAL, new String( stringBytes ) );
    }

    public void testWholeProcess()
    {
         String encrypted = UtilsEncryption.encrypt( ORIGINAL );
         assertNotNull( "The encrypted result should not be null", encrypted );
         assertTrue( "The encrypted result should not be empty", encrypted.length() > 0 );

         String decrypted = UtilsEncryption.decrypt( encrypted );
         assertNotNull( "The decrypted result should not be null", decrypted );
         assertTrue( "The decrypted result should not be empty", decrypted.length() > 0 );

         assertEquals( "Something went wrong", ORIGINAL, decrypted );
}

}

例外をスローする行は次のとおりです。

byte[] encrypted = cipher.doFinal( value );

完全なスタック トレースは次のとおりです。

    W/<package>.UtilsEncryption:decrypt(16414): pad block corrupted
    W/System.err(16414): javax.crypto.BadPaddingException: pad block corrupted
    W/System.err(16414):    at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:709)
    W/System.err(16414):    at javax.crypto.Cipher.doFinal(Cipher.java:1111)
    W/System.err(16414):    at <package>.UtilsEncryption.process(UtilsEncryption.java:117)
    W/System.err(16414):    at <package>.UtilsEncryption.decrypt(UtilsEncryption.java:69)
    W/System.err(16414):    at <package>.UtilsEncryptionTest.testWholeProcess(UtilsEncryptionTest.java:74)
    W/System.err(16414):    at java.lang.reflect.Method.invokeNative(Native Method)
    W/System.err(16414):    at java.lang.reflect.Method.invoke(Method.java:511)
    W/System.err(16414):    at junit.framework.TestCase.runTest(TestCase.java:168)
    W/System.err(16414):    at junit.framework.TestCase.runBare(TestCase.java:134)
    W/System.err(16414):    at junit.framework.TestResult$1.protect(TestResult.java:115)
    W/System.err(16414):    at junit.framework.TestResult.runProtected(TestResult.java:133)
D/elapsed (  588): 14808
    W/System.err(16414):    at junit.framework.TestResult.run(TestResult.java:118)
    W/System.err(16414):    at junit.framework.TestCase.run(TestCase.java:124)
    W/System.err(16414):    at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:190)
    W/System.err(16414):    at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:175)
    W/System.err(16414):    at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:555)
    W/System.err(16414):    at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1661)

何が起こっているのか手がかりを持っている人はいますか?参照されているクラスのいずれかで Android 4.2 の重大な変更を知っている人はいますか?

どうもありがとう

4

3 に答える 3

62

Android Jellybean ページから:

OpenSSL を使用するように SecureRandom と Cipher.RSA のデフォルトの実装を変更しました。

SecureRandom以前の Crypto プロバイダーの代わりに OpenSSL を使用するように、デフォルトのプロバイダーを変更しました。

次のコードは、Android 4.2 より前のバージョンと Android 4.2 で 2 つの異なる出力を生成します。

SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
Log.i(TAG, "rand.getProvider(): " + rand.getProvider().getName());

4.2 より前のデバイスの場合:

rand.getProvider: 暗号

4.2 デバイスの場合:

rand.getProvider: AndroidOpenSSL

幸いなことに、以前の動作に簡単に戻すことができます。

SecureRandom sr = SecureRandom.getInstance( "SHA1PRNG", "Crypto" );

確かに、次のように述べているJavadocSecureRandom.setSeedに照らして呼び出すのは危険です。

SecureRandom のシードは安全でない可能性があります

シードは、乱数生成のブートストラップに使用されるバイト配列です。暗号的に安全な乱数を生成するには、シードとアルゴリズムの両方が安全でなければなりません。

デフォルトでは、このクラスのインスタンスは、/dev/urandom などの内部エントロピー ソースを使用して初期シードを生成します。このシードは予測不可能であり、安全な使用に適しています。

代わりに、シードされたコンストラクターを使用して明示的に初期シードを指定するか、乱数が生成される前に setSeed(byte[]) を呼び出して指定することもできます。固定シードを指定すると、インスタンスは予測可能な数列を返します。これはテストには役立つかもしれませんが、安全な使用には適していません。

ただし、単体テストを作成する場合は、あなたが行っているように、使用してsetSeedも問題ない場合があります。

于 2012-11-14T21:10:33.573 に答える
3

Brighamが指摘したように、Android 4.2 ではセキュリティが強化SecureRandomされ、 Crypto から OpenSSL へのデフォルトの実装が更新されました。

暗号化- OpenSSL を使用するように SecureRandom と Cipher.RSA のデフォルトの実装を変更しました。OpenSSL 1.0.1 を使用した TLSv1.1 および TLSv1.2 の SSL ソケット サポートを追加

ブリガムの答えは一時的な解決策であり、問​​題は解決しますが、まだ間違った方法をとっているため、お勧めしません。

推奨される方法 ( Nelenkov のチュートリアルを確認してください) は、適切なキー派生 PKCS (Public Key Cryptography Standard) を使用することです。これは、PBKDF1 と PBKDF2 の 2 つのキー派生関数を定義し、PBKDF2 がより推奨されます。

これが鍵の入手方法です。

    int iterationCount = 1000;
    int saltLength = 8; // bytes; 64 bits
    int keyLength = 256;
    SecureRandom random = new SecureRandom();
    byte[] salt = new byte[saltLength];
    random.nextBytes(salt);
    KeySpec keySpec = new PBEKeySpec(seed.toCharArray(), salt,
            iterationCount, keyLength);
    SecretKeyFactory keyFactory = SecretKeyFactory
            .getInstance("PBKDF2WithHmacSHA1");
    byte[] raw = keyFactory.generateSecret(keySpec).getEncoded();
于 2015-02-01T14:03:31.917 に答える