Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/219.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 尝试在GCM模式下解密消息时获取AEADBadTagException_Java_Android_Encryption_Cryptography_Android Keystore - Fatal编程技术网

Java 尝试在GCM模式下解密消息时获取AEADBadTagException

Java 尝试在GCM模式下解密消息时获取AEADBadTagException,java,android,encryption,cryptography,android-keystore,Java,Android,Encryption,Cryptography,Android Keystore,我正在编写一个应用程序,它有很多安全限制: 它需要存储安全加密的文件,并且必须能够对其进行解密。此外,操作员需要能够在没有应用程序的情况下解密文件 为了存档,我在我的电脑上生成了一个密钥对,将公共部分放入我的应用程序中,在应用程序中生成一个AESSecretKey密钥,用我的公钥加密并保存(用于操作员),然后将未加密的密钥放入AndroidKeyStore 要加密消息,我从KeyStore接收SecretKey,加密我的消息,获取我使用的IV以及加密SecretKey,并按照定义的顺序将它们写入

我正在编写一个应用程序,它有很多安全限制: 它需要存储安全加密的文件,并且必须能够对其进行解密。此外,操作员需要能够在没有应用程序的情况下解密文件

为了存档,我在我的电脑上生成了一个
密钥对
,将公共部分放入我的应用程序中,在应用程序中生成一个
AES
SecretKey
密钥,用我的公钥加密并保存(用于操作员),然后将未加密的密钥放入
AndroidKeyStore

要加密消息,我从
KeyStore
接收
SecretKey
,加密我的消息,获取我使用的
IV
以及加密SecretKey,并按照定义的顺序将它们写入字节数组(IV->encryptedSecretKey->encryptedMessage)

为了解密,我尝试了相反的方法:获取字节数组,读取iv和encryptedSecretKey,然后将其余的(encryptedMessage)传递给我的密码进行解密。 问题是,
cipher.doFinal(encryptedMessage)
正在抛出
javax.crypto.aeadbadtag异常
android.security.KeyStoreException:Signature/MAC验证失败
引起

我已经检查了加密的消息和我想要解密的消息是否完全相同。我不知道我做错了什么

我使用的类如下所示:

package my.company.domain;

import android.content.Context;
import android.content.SharedPreferences;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.support.annotation.NonNull;
import android.util.Base64;
import android.util.Log;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.X509EncodedKeySpec;

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

public class CryptoHelper {

    public static final String TAG = CryptoHelper.class.getSimpleName();

    private static final String KEY_ALIAS = "OI1lTI1lLI1l0";
    private static final char[] KEY_PASSWORD = "Il0VELI1lO".toCharArray();

    private static final String PREF_NAME = "CryptoPrefs";
    private static final String KEY_ENCRYPTED_SECRET = "encryptedSecret";

    private static final String ANDROID_KEY_STORE = "AndroidKeyStore";

    private static final int    IV_SIZE = 12;
    private static final int    IV_BIT_LEN = IV_SIZE * 8;


    //generate 128 bit key (16), other possible values 192(24), 256(32)
    private static final int    AES_KEY_SIZE = 16;
    private static final String AES = KeyProperties.KEY_ALGORITHM_AES;
    private static final String AES_MODE = AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE;

    private static final String RSA = KeyProperties.KEY_ALGORITHM_RSA;
    private static final String RSA_MODE = KeyProperties.KEY_ALGORITHM_RSA + "/" + KeyProperties.BLOCK_MODE_ECB + "/" + KeyProperties.ENCRYPTION_PADDING_NONE;
    private static final String RSA_PROVIDER = "AndroidOpenSSL";

    private final Context mContext;
    private final SharedPreferences mPrefs;

    private SecureRandom mSecureRandom;
    private KeyStore mAndroidKeyStore;
    private PublicKey mPublicKey;
    private byte[] mEncryptedSecretKey;

    public CryptoHelper(Context context) {
        mContext = context;
        mSecureRandom = new SecureRandom();
        mPrefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
        try {
            mAndroidKeyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
            mAndroidKeyStore.load(null);

        } catch (KeyStoreException e) {
            Log.wtf(TAG, "Could not get AndroidKeyStore!", e);
        } catch (Exception e) {
            Log.wtf(TAG, "Could not load AndroidKeyStore!", e);
        }
    }

    public void reset() throws KeyStoreException {
        mAndroidKeyStore.deleteEntry(KEY_ALIAS);
    }

    public byte[] encrypt(byte[] message){
        SecretKey secretKey = getSecretKey();
        try {
            Cipher cipher = Cipher.getInstance(AES_MODE);
            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());

            byte[] cryptedBytes = cipher.doFinal(message);
            byte[] iv = cipher.getIV();
            byte[] encryptedSecretKey = getEncryptedSecretKey();
            ByteBuffer buffer = ByteBuffer.allocate(IV_BIT_LEN + encryptedSecretKey.length + cryptedBytes.length);
            buffer
                .put(iv)
                .put(encryptedSecretKey)
                .put(cryptedBytes);
            return buffer.array();
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
        return null;
    }

    public byte[] encrypt(String message){
        return encrypt(message.getBytes(StandardCharsets.UTF_8));
    }

    public byte[] decrypt(byte[] bytes){
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        byte[] iv = new byte[IV_SIZE];
        buffer.get(iv);
        byte[] unused = getEncryptedSecretKey();
        buffer.get(unused);
        byte[] encryptedMessage = new byte[bytes.length - IV_SIZE - unused.length];
        buffer.get(encryptedMessage);
        try {
            Cipher cipher = Cipher.getInstance(AES_MODE);
            GCMParameterSpec parameterSpec = new GCMParameterSpec(IV_BIT_LEN, iv);
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), parameterSpec);
            byte[] decryptedMessage = cipher.doFinal(encryptedMessage);
            return decryptedMessage;
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String decryptToString(byte[] bytes){
        return new String(decrypt(bytes), StandardCharsets.UTF_8);
    }

    public byte[] decrypt(FileInputStream fileToDecrypt){
        byte[] buffer = null;
        try {
            buffer = new byte[fileToDecrypt.available()];
            fileToDecrypt.read(buffer);
            buffer = decrypt(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buffer;
    }


    public PublicKey getPublicKey() {
        if (null == mPublicKey) {
            mPublicKey = readPublicKey();
        }
        return mPublicKey;
    }

    public byte[] getEncryptedSecretKey() {
        if (null == mEncryptedSecretKey){
            mEncryptedSecretKey = Base64.decode(mPrefs.getString(KEY_ENCRYPTED_SECRET, null), Base64.NO_WRAP);
        }
        return mEncryptedSecretKey;
    }

    private void saveEncryptedSecretKey(byte[] encryptedSecretKey){
        String base64EncryptedKey = Base64.encodeToString(encryptedSecretKey, Base64.NO_WRAP);
        mPrefs.edit().putString(KEY_ENCRYPTED_SECRET, base64EncryptedKey).apply();
    }

    protected SecretKey getSecretKey(){
        SecretKey secretKey = null;
        try {
            if (!mAndroidKeyStore.containsAlias(KEY_ALIAS)){
               generateAndStoreSecureKey();
            }
            secretKey = (SecretKey) mAndroidKeyStore.getKey(KEY_ALIAS, KEY_PASSWORD);
        } catch (KeyStoreException e) {
            Log.wtf(TAG, "Could not check AndroidKeyStore alias!", e);
            secretKey = null;
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
            secretKey = null;
        }
        return secretKey;
    }

    private void generateAndStoreSecureKey()
            throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, KeyStoreException, BadPaddingException, IllegalBlockSizeException {
        SecretKey secretKey = generateSecureRandomKey();
        PublicKey publicKey = getPublicKey();
        Cipher keyCipher = Cipher.getInstance(RSA_MODE, RSA_PROVIDER);
        keyCipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedSecretKeyBytes = keyCipher.doFinal(secretKey.getEncoded());

        saveEncryptedSecretKey(encryptedSecretKeyBytes);

        KeyProtection keyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_VERIFY)
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .build();
        mAndroidKeyStore.setEntry(KEY_ALIAS, new KeyStore.SecretKeyEntry(secretKey), keyProtection);
    }


    protected PublicKey readPublicKey() {
        DataInputStream dis = null;
        PublicKey key = null;
        try {
            dis = new DataInputStream(mContext.getResources().getAssets().open("public_key.der"));
            byte[] keyBytes = new byte[dis.available()];
            dis.readFully(keyBytes);

            X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
            KeyFactory facotory = KeyFactory.getInstance(RSA);
            key = facotory.generatePublic(spec);
        } catch (Exception e) {
            key = null;
        } finally {
            if (null != dis) {
                try {
                    dis.close();
                } catch (IOException e) {
                    Log.wtf(TAG, "Cannot Close Stream!", e);
                }
            }
        }
        return key;
    }

    @NonNull
    protected SecretKey generateSecureRandomKey() {
        return new SecretKeySpec(generateSecureRandomBytes(AES_KEY_SIZE), AES);
    }

    @NonNull
    protected byte[] generateSecureRandomBytes(int byteCount) {
        byte[] keyBytes = new byte[byteCount];
        mSecureRandom.nextBytes(keyBytes);
        return keyBytes;
    }
}
我这样测试它:

@Test
public void testCrypto() throws Exception {
    CryptoHelper crypto = new CryptoHelper(InstrumentationRegistry.getTargetContext());
    crypto.reset();
    String verySecretOpinion = "we're all doomed";
    byte[] encrypt = crypto.encrypt(verySecretOpinion);
    Assert.assertNotNull("Encrypted secret is Null!", encrypt);
    Assert.assertFalse("encrypted Bytes are the same as Input!", new String(encrypt, StandardCharsets.UTF_8).equals(verySecretOpinion));
    String decryptedString = crypto.decryptToString(encrypt);
    Assert.assertNotNull("Decrypted String must be Non-Null!", decryptedString);
    Assert.assertEquals("Decrypted String doesn't equal encryption input!", verySecretOpinion, decryptedString);
}
顺便说一下,这个版本是25,所以比棉花糖高

更新

  • 修复了将SecretKey thx保存到James K Polk注释时的
    Cipher.DECRYPT_模式
    ENCRYPT_模式
  • 如果我从
    BlockMode
    GCM
    切换到BlockMode
    CBC
    (并将
    GCMParameterSpec
    更改为
    ivparameterspec
    ,但失去了对GCM模式的验证,则此功能正常

  • 操作员界面至少存在两个问题。首先,您使用错误的密码模式对密钥进行RSA加密:您在本应使用加密的情况下使用了解密模式。其次,您使用的RSA没有任何填充。您需要使用实填充模式,建议使用OEAP填充模式之一

    调整用于保存结果的缓冲区大小时,加密端出错:

    ByteBuffer buffer = ByteBuffer.allocate(IV_BIT_LEN + encryptedSecretKey.length + cryptedBytes.length);
    
    分配的空间太多。
    IV_位长度
    可能应更改为
    IV_大小
    ,以获得正确大小的
    字节缓冲

    最后一个错误是,在解密端设置
    GCMParameterSpec
    时,未能说明GCM身份验证标记长度。您在此行中初始化了标记长度

    GCMParameterSpec parameterSpec = new GCMParameterSpec(IV_BIT_LEN, iv);
    
    但这是不正确的,标记长度与IV无关。因为没有在加密端显式设置GCMParameterSpec,所以得到了默认标记长度,正好是128

    您可以通过调用
    cipher.getParameters().getParameterSpec(gcmpareterSpec.class)为了获得参数规格,从这里你可以检索标签长度和IV。你应该考虑标签长度,16字节=128比特,是一个硬编码常量,而不是传输它。接收器应该类似的假设标签长度是128位。嘿,谢谢漂亮的捕捉,将改正这个。但是我想,我的问题不是。与RSA加密相关。我尝试使用
    PKCS1
    ,但在尝试加密SecretKey时出现异常。将在我的问题中更正此问题anyway@RafaelT:是的,我正在查看您现在收到的具体错误。希望我很快会对此答案有所补充。我在
    IV_BIT_LEN
    昨天我自己,但是IV_BIT_LEN会杀了我的。非常感谢,它现在可以工作了。你能解释一下为什么我需要RSA的填充模式,不能不添加就使用它吗?首先,没有填充的RSA在你使用它的方式中是不安全的。其次,填充方案包含的信息允许他们只提取数据这是加密的。在没有填充的情况下,解密后您将获得RSA明文块的全部内容。换句话说,您加密了16个字节,但如果不使用填充,您将返回128个字节。