Android 4.2 上的加密错误 [英] Encryption error on Android 4.2

查看:24
本文介绍了Android 4.2 上的加密错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下代码适用于除最新的 4.2 以外的所有 android 版本

The following code is working on all the versions of android except the latest 4.2

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();
    }
}

这是我为了重现错误而创建的一个小型单元测试

Here's a small unit test that i've created to reproduce the error

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 在任何引用的类中发生了重大变化?

Does anybody have a clue of what might be happening ? Is anybody aware of a breaking change on android 4.2 in any of the referenced classes?

非常感谢

推荐答案

来自 Android Jellybean 页面:

将 SecureRandom 和 Cipher.RSA 的默认实现修改为使用 OpenSSL

Modified the default implementations of SecureRandom and Cipher.RSA to use OpenSSL

他们将 SecureRandom 的默认提供程序更改为使用 OpenSSL 而不是之前的加密提供程序.

They changed the default provider for SecureRandom to use OpenSSL instead of the previous Crypto provider.

以下代码将在 Android 4.2 和 Android 4.2 之前的版本上产生两种不同的输出:

The following code will produce two different outputs on pre-Android 4.2 and Android 4.2:

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

在 4.2 之前的设备上:

On pre-4.2 devices:

rand.getProvider:加密

rand.getProvider: Crypto

在 4.2 设备上:

rand.getProvider:AndroidOpenSSL

rand.getProvider: AndroidOpenSSL

幸运的是,很容易恢复到旧的行为:

Fortunately, it's easy to revert to the old behavior:

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

可以肯定的是,根据 Javadocs 哪个状态:

To be sure, it's dangerous to be calling SecureRandom.setSeed at all in light of the Javadocs which state:

播种 SecureRandom 可能不安全

Seeding SecureRandom may be insecure

种子是用于引导随机数生成的字节数组.要生成加密安全的随机数,种子和算法都必须是安全的.

A seed is an array of bytes used to bootstrap random number generation. To produce cryptographically secure random numbers, both the seed and the algorithm must be secure.

默认情况下,此类的实例将使用内部熵源(例如/dev/urandom)生成初始种子.此种子不可预测,适合安全使用.

By default, instances of this class will generate an initial seed using an internal entropy source, such as /dev/urandom. This seed is unpredictable and appropriate for secure use.

您也可以在生成任何随机数之前使用带种子的构造函数或通过调用 setSeed(byte[]) 显式指定初始种子.指定固定种子将导致实例返回可预测的数字序列.这可能对测试有用,但不适合安全使用.

You may alternatively specify the initial seed explicitly with the seeded constructor or by calling setSeed(byte[]) before any random numbers have been generated. Specifying a fixed seed will cause the instance to return a predictable sequence of numbers. This may be useful for testing but it is not appropriate for secure use.

但是,对于编写单元测试,正如您所做的那样,使用 setSeed 可能没问题.

However, for writing unit tests, as you are doing, using setSeed may be okay.

这篇关于Android 4.2 上的加密错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆