Android加密在发布应用程序上失败,但在调试时失败

时间:2017-05-01 21:43:05

标签: java android encryption

我使用下面的实用程序类来加密Android上的敏感数据。我使用下面的方法,因为我希望它适用于运行Android L或更早版本的设备。

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.util.Base64;

import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.util.Calendar;

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;
import javax.security.auth.x500.X500Principal;

public class Encryptor
{
    private static final String KEY_ALIAS = "xxxxxx";
    private static final String SECRET_PREF_NAME = "xxxxx";
    private static final String SECRET_KEY_NAME = "xxxxx";
    private static final String RSA_MODE = "RSA/ECB/PKCS1Padding";
    private static final String AES_MODE = "AES/ECB/PKCS7Padding";

    private static  KeyStore.PrivateKeyEntry getKeyPairEntry(Context ctx)
        throws
            IOException,
            KeyStoreException,
            NoSuchAlgorithmException,
            NoSuchProviderException,
            InvalidAlgorithmParameterException,
            UnrecoverableEntryException,
            CertificateException
    {
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);
        if (!keyStore.containsAlias(KEY_ALIAS))
        {
            // Generate a key pair for encryption
            Calendar start = Calendar.getInstance();
            Calendar end = Calendar.getInstance();
            end.add(Calendar.YEAR, 10);
            KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx)
                .setAlias(KEY_ALIAS)
                .setSubject(new X500Principal("CN=" + KEY_ALIAS))
                .setSerialNumber(BigInteger.TEN)
                .setStartDate(start.getTime())
                .setEndDate(end.getTime())
                .build();
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
            kpg.initialize(spec);
            kpg.generateKeyPair();
        }

        return (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
    }

    private static  byte[] encryptSecret(Context ctx, byte[] secret)
            throws
            IOException,
            KeyStoreException,
            NoSuchAlgorithmException,
            NoSuchProviderException,
            InvalidAlgorithmParameterException,
            UnrecoverableEntryException,
            CertificateException,
            NoSuchPaddingException,
            InvalidKeyException,
            BadPaddingException,
            IllegalBlockSizeException
    {
        KeyStore.PrivateKeyEntry privateKeyEntry = getKeyPairEntry(ctx);

        Cipher cipher;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
        {
            cipher = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL"); // error in android 6: InvalidKeyException: Need RSA private or public key
        }
        else
        {
            cipher = Cipher.getInstance(RSA_MODE, "AndroidKeyStoreBCWorkaround"); // error in android 5: NoSuchProviderException: Provider not available: AndroidKeyStoreBCWorkaround
        }

        cipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.getCertificate().getPublicKey());
        return cipher.doFinal(secret);
    }

    private static  byte[] decryptSecret(Context ctx, byte[] encrypted)
            throws
            UnrecoverableEntryException,
            NoSuchAlgorithmException,
            IOException,
            KeyStoreException,
            CertificateException,
            NoSuchProviderException,
            InvalidAlgorithmParameterException,
            NoSuchPaddingException,
            InvalidKeyException,
            BadPaddingException,
            IllegalBlockSizeException
    {
        KeyStore.PrivateKeyEntry privateKeyEntry = getKeyPairEntry(ctx);

        Cipher cipher;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
        {
            cipher = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL"); // error in android 6: InvalidKeyException: Need RSA private or public key
        }
        else
        {
            cipher = Cipher.getInstance(RSA_MODE, "AndroidKeyStoreBCWorkaround"); // error in android 5: NoSuchProviderException: Provider not available: AndroidKeyStoreBCWorkaround
        }

        cipher.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());
        return cipher.doFinal(encrypted);
    }

    private static  void generateSecretIfNecessary(Context ctx)
            throws
            IOException,
            CertificateException,
            NoSuchAlgorithmException,
            InvalidKeyException,
            UnrecoverableEntryException,
            InvalidAlgorithmParameterException,
            NoSuchPaddingException,
            NoSuchProviderException,
            KeyStoreException,
            BadPaddingException,
            IllegalBlockSizeException
    {
        SharedPreferences pref = ctx.getSharedPreferences(SECRET_PREF_NAME, Context.MODE_PRIVATE);
        String encryptedKeyB64 = pref.getString(SECRET_KEY_NAME, null);
        if (encryptedKeyB64 == null)
        {
            KeyGenerator gen = KeyGenerator.getInstance("AES");
            gen.init(256); /* 256-bit AES */
            SecretKey secret = gen.generateKey();
            byte[] encryptedKey = encryptSecret(ctx, secret.getEncoded());
            encryptedKeyB64 = Base64.encodeToString(encryptedKey, Base64.DEFAULT);
            SharedPreferences.Editor edit = pref.edit();
            edit.putString(SECRET_KEY_NAME, encryptedKeyB64);
            edit.commit();
        }
    }

    private static Key getSecret(Context ctx)
            throws
            IOException,
            CertificateException,
            NoSuchAlgorithmException,
            InvalidKeyException,
            UnrecoverableEntryException,
            InvalidAlgorithmParameterException,
            NoSuchPaddingException,
            KeyStoreException,
            NoSuchProviderException,
            BadPaddingException,
            IllegalBlockSizeException
    {
        generateSecretIfNecessary(ctx);

        SharedPreferences pref = ctx.getSharedPreferences(SECRET_PREF_NAME, Context.MODE_PRIVATE);
        String encryptedSecretB64 = pref.getString(SECRET_KEY_NAME, null);
        byte[] encryptedSecret = Base64.decode(encryptedSecretB64, Base64.DEFAULT);
        byte[] key = decryptSecret(ctx, encryptedSecret);
        return new SecretKeySpec(key, "AES");
    }

    public static String encryptData(Context ctx, byte[] input) throws EncryptorException
    {
        try
        {
            Cipher c = Cipher.getInstance(AES_MODE, "BC");
            c.init(Cipher.ENCRYPT_MODE, getSecret(ctx));
            byte[] encryptedBytes = c.doFinal(input);
            return Base64.encodeToString(encryptedBytes, Base64.DEFAULT);
        }
        catch (InvalidKeyException | BadPaddingException | IOException | NoSuchPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | CertificateException | NoSuchProviderException | NoSuchAlgorithmException | KeyStoreException | UnrecoverableEntryException e)
        {
            throw new EncryptorException("Failed to encrypt data", e);
        }
    }

    public static  byte[] decryptData(Context ctx, String encryptedBase64Encoded) throws EncryptorException
    {
        try
        {
            Cipher c = Cipher.getInstance(AES_MODE, "BC");
            c.init(Cipher.DECRYPT_MODE, getSecret(ctx));
            byte[] encrypted = Base64.decode(encryptedBase64Encoded, Base64.DEFAULT);
            byte[] decodedBytes = c.doFinal(encrypted);
            return decodedBytes;
        }
        catch (InvalidKeyException | BadPaddingException | IOException | NoSuchPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | CertificateException | NoSuchProviderException | NoSuchAlgorithmException | KeyStoreException | UnrecoverableEntryException e)
        {
            throw new EncryptorException("Failed to decrypt data", e);
        }
    }
}

当我在我的设备上运行我的Android应用时,这一切都有效。但是,只要我创建一个签名的APK,将其推送到Android Play商店,然后将其推送到我的设备,加密就不再适用了。我得到一个Keystore异常:未知错误。

堆栈跟踪:

05-01 17:32:48.813 16914-16914/? E/xxxxxxxxxxxxx: Failed to save user data
    xxxxxxxxxxxxxxxxxx.utils.crypto.EncryptorException: Failed to encrypt data
    at xxxxxxxxxxxxxxxxxx.utils.crypto.Encryptor.encryptData(Encryptor.java:213)
    at xxxxxxxxxxxxxxxxxx.utils.storage.xxxxxxxxxxxxx.saveUserData(xxxxxxxxxxxxx.java:49)
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$24$1$2.onDone(xxxxxxxxx.java:1079)
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$24$1$2.onDone(xxxxxxxxx.java:1074)
    at org.jdeferred.impl.AbstractPromise.triggerDone(AbstractPromise.java:107)
    at org.jdeferred.android.AndroidDeferredObject.triggerDone(AndroidDeferredObject.java:104)
    at org.jdeferred.impl.AbstractPromise.triggerDone(AbstractPromise.java:98)
    at org.jdeferred.impl.DeferredObject.resolve(DeferredObject.java:70)
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$3.onResponse(xxxxxxxxx.java:155)
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$3.onResponse(xxxxxxxxx.java:147)
    at com.android.volley.toolbox.StringRequest.deliverResponse(StringRequest.java:67)
    at com.android.volley.toolbox.StringRequest.deliverResponse(StringRequest.java:30)
    at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable.run(ExecutorDelivery.java:99)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6290)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Caused by: javax.crypto.IllegalBlockSizeException
    at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:519)
    at javax.crypto.Cipher.doFinal(Cipher.java:2056)
    at xxxxxxxxxxxxxxxxxx.utils.crypto.Encryptor.decryptSecret(Encryptor.java:145)
    at xxxxxxxxxxxxxxxxxx.utils.crypto.Encryptor.getSecret(Encryptor.java:198)
    at xxxxxxxxxxxxxxxxxx.utils.crypto.Encryptor.encryptData(Encryptor.java:207)
    at xxxxxxxxxxxxxxxxxx.utils.storage.xxxxxxxxxxxxx.saveUserData(xxxxxxxxxxxxx.java:49) 
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$24$1$2.onDone(xxxxxxxxx.java:1079) 
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$24$1$2.onDone(xxxxxxxxx.java:1074) 
    at org.jdeferred.impl.AbstractPromise.triggerDone(AbstractPromise.java:107) 
    at org.jdeferred.android.AndroidDeferredObject.triggerDone(AndroidDeferredObject.java:104) 
    at org.jdeferred.impl.AbstractPromise.triggerDone(AbstractPromise.java:98) 
    at org.jdeferred.impl.DeferredObject.resolve(DeferredObject.java:70) 
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$3.onResponse(xxxxxxxxx.java:155) 
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$3.onResponse(xxxxxxxxx.java:147) 
    at com.android.volley.toolbox.StringRequest.deliverResponse(StringRequest.java:67) 
    at com.android.volley.toolbox.StringRequest.deliverResponse(StringRequest.java:30) 
    at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable.run(ExecutorDelivery.java:99) 
    at android.os.Handler.handleCallback(Handler.java:751) 
    at android.os.Handler.dispatchMessage(Handler.java:95) 
    at android.os.Looper.loop(Looper.java:154) 
    at android.app.ActivityThread.main(ActivityThread.java:6290) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776) 
Caused by: android.security.KeyStoreException: Unknown error
    at android.security.KeyStore.getKeyStoreException(KeyStore.java:666)
    at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224)
    at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506)
    at javax.crypto.Cipher.doFinal(Cipher.java:2056) 
    at xxxxxxxxxxxxxxxxxx.utils.crypto.Encryptor.decryptSecret(Encryptor.java:145) 
    at xxxxxxxxxxxxxxxxxx.utils.crypto.Encryptor.getSecret(Encryptor.java:198) 
    at xxxxxxxxxxxxxxxxxx.utils.crypto.Encryptor.encryptData(Encryptor.java:207) 
    at xxxxxxxxxxxxxxxxxx.utils.storage.xxxxxxxxxxxxx.saveUserData(xxxxxxxxxxxxx.java:49) 
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$24$1$2.onDone(xxxxxxxxx.java:1079) 
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$24$1$2.onDone(xxxxxxxxx.java:1074) 
    at org.jdeferred.impl.AbstractPromise.triggerDone(AbstractPromise.java:107) 
    at org.jdeferred.android.AndroidDeferredObject.triggerDone(AndroidDeferredObject.java:104) 
    at org.jdeferred.impl.AbstractPromise.triggerDone(AbstractPromise.java:98) 
    at org.jdeferred.impl.DeferredObject.resolve(DeferredObject.java:70) 
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$3.onResponse(xxxxxxxxx.java:155) 
    at xxxxxxxxxxxxxxxxxx.client.xxxxxxxxx$3.onResponse(xxxxxxxxx.java:147) 
    at com.android.volley.toolbox.StringRequest.deliverResponse(StringRequest.java:67) 
    at com.android.volley.toolbox.StringRequest.deliverResponse(StringRequest.java:30) 
    at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable.run(ExecutorDelivery.java:99) 
    at android.os.Handler.handleCallback(Handler.java:751) 
    at android.os.Handler.dispatchMessage(Handler.java:95) 
    at android.os.Looper.loop(Looper.java:154) 
    at android.app.ActivityThread.main(ActivityThread.java:6290) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776) 

我无法进行太多调试,因为错误并没有真正指向我。如果有人在这里可以提供帮助,我真的很感激。尝试加密/解密时,我也可能做错了。我不确定我完全知道我在做什么。任何指针都会非常感激。

以下是我的清单权限:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          package="xxxxxxxxxxxxxxx">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="com.example.permission.MAPS_RECEIVE"/>
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MANAGE_DOCUMENTS"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

</manifest>

以下是我的gradle默认配置:

android {
    compileSdkVersion 25
    buildToolsVersion '25.0.0'

    defaultConfig {
        applicationId "com.bantuapp.bantu"
        minSdkVersion 18
        targetSdkVersion 25
        versionCode 44
        versionName "1.0"
        multiDexEnabled true
        vectorDrawables {
            useSupportLibrary true
        }
    }
}

1 个答案:

答案 0 :(得分:0)

看起来您在加密的有效负载上缺少填充,请确保您设置了填充,例如:AES/CBC/PKCS7Padding

IllegalBlockSizeException表示要解密的数据与密码的块大小不匹配。