You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android生成的RSA公钥经WebCrypto加密后解密抛IllegalBlockSizeException求助

问题:跨端RSA OAEP加密后Android解密抛出IllegalBlockSizeException

我生成RSA密钥对,将公钥Base64编码后存入数据库,Chrome扩展取出公钥加密数据,但加密后的数据在Android端解密时,cipher.doFinal(encryptedData)抛出javax.crypto.IllegalBlockSizeException异常。Android自身加密的数据可正常解密,双方使用的是RSA/NONE/OAEPwithSHA-256andMGF1Padding算法。

Android端生成密钥与导出公钥代码

class EnCryptor {

    private static final String TRANSFORMATION = "RSA/NONE/OAEPwithSHA-256andMGF1Padding";
    private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
    private KeyStore keyStore;

    private byte[] encryption;

    EnCryptor() throws CertificateException, NoSuchAlgorithmException, KeyStoreException,
            IOException {
        initKeyStore();
    }

    private void initKeyStore() throws KeyStoreException, CertificateException,
            NoSuchAlgorithmException, IOException {
        keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
        keyStore.load(null);
    }

    byte[] encryptText(final String alias, final String textToEncrypt)
            throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException,
            NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IOException,
            InvalidAlgorithmParameterException, SignatureException, BadPaddingException,
            IllegalBlockSizeException {

        final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(alias));

        return (encryption = cipher.doFinal(textToEncrypt.getBytes("UTF-8")));
    }

    @NonNull
    private PublicKey getSecretKey(final String alias) throws NoSuchAlgorithmException,
            NoSuchProviderException, InvalidAlgorithmParameterException, UnrecoverableEntryException, KeyStoreException {

        if (keyStore.getCertificate(alias) != null){
            PublicKey pubKey = keyStore.getCertificate(alias).getPublicKey();
            String publicKeyString = Base64.encodeToString(pubKey.getEncoded(), 2);
            return keyStore.getCertificate(alias).getPublicKey();
        }
        else {
            final KeyPairGenerator keyPairGenerator = KeyPairGenerator
                    .getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE);

            keyPairGenerator.initialize(new KeyGenParameterSpec.Builder(alias,
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                    .setKeySize(2048)
                    .setDigests(KeyProperties.DIGEST_SHA256)
                    .build());

            PublicKey pubKey = keyPairGenerator.genKeyPair().getPublic();
            String publicKeyString = Base64.encodeToString(pubKey.getEncoded(), 2);

            return keyPairGenerator.genKeyPair().getPublic();
        }
    }
}

Chrome扩展加密代码

function encryptUsingImportedKey(pubKey,data){

        const fromBase64 = buffer =>
            Uint8Array.from(atob(buffer), c => c.charCodeAt(0));

        var pubKeyArray = fromBase64(pubKey);

        keyMaterial = await window.crypto.subtle.importKey(
            'spki',
            pubKeyArray,
            {name: 'RSA-OAEP', hash: 'SHA-256',},
            false,
            ['encrypt']
        );

        const encoder = new TextEncoder();
        const encoded_data = encoder.encode(data);
        const toBase64 = buffer =>
            btoa(String.fromCharCode(...new Uint8Array(buffer)));

        var encrypted_array = await window.crypto.subtle.encrypt(
            {
                name: "RSA-OAEP",
            },
            keyMaterial,
            encoded_data
        )

        var encrypted = toBase64(encrypted_array);
}       

Android端解密代码

class DeCryptor {

    private static final String TRANSFORMATION = "RSA/NONE/OAEPwithSHA-256andMGF1Padding";
    private static final String ANDROID_KEY_STORE = "AndroidKeyStore";

    private KeyStore keyStore;

    DeCryptor() throws CertificateException, NoSuchAlgorithmException, KeyStoreException,
            IOException {
        initKeyStore();
    }

    private void initKeyStore() throws KeyStoreException, CertificateException,
            NoSuchAlgorithmException, IOException {
        keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
        keyStore.load(null);
    }

    String decryptData(final String alias, final byte[] encryptedData)
            throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException,
            NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IOException,
            BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {

        final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        KeyStore.Entry secretKey = keyStore.getEntry(alias, null);
        cipher.init(Cipher.DECRYPT_MODE, getSecretKey(alias));
        byte[] decryptData = cipher.doFinal(encryptedData);

        return new String(decryptData, "UTF-8");
    }

    private PrivateKey getSecretKey(final String alias) throws NoSuchAlgorithmException,
            UnrecoverableEntryException, KeyStoreException {
        return ((KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null)).getPrivateKey();
    }
}

问题根源分析

  1. 密钥生成重复:Android端getSecretKey方法中,生成新密钥对时调用了两次keyPairGenerator.genKeyPair(),导致数据库存储的公钥与AndroidKeyStore中实际保存的密钥对不匹配,Chrome用错误公钥加密的数据自然无法被正确私钥解密。
  2. 异步函数错误:Chrome扩展的encryptUsingImportedKey使用了await但未声明为async,导致语法错误,加密逻辑无法正常执行,返回的密文数据无效。
  3. 变量未声明/返回:Chrome代码中keyMaterial、encrypted_array等变量未用const/let声明,且未返回加密结果,调用方无法获取有效密文。

修复方案

1. 修复Android端密钥生成问题

修改getSecretKey方法,只生成一次密钥对:

@NonNull
private PublicKey getSecretKey(final String alias) throws NoSuchAlgorithmException,
        NoSuchProviderException, InvalidAlgorithmParameterException, UnrecoverableEntryException, KeyStoreException {

    if (keyStore.getCertificate(alias) != null){
        PublicKey pubKey = keyStore.getCertificate(alias).getPublicKey();
        String publicKeyString = Base64.encodeToString(pubKey.getEncoded(), Base64.NO_WRAP);
        return pubKey;
    }
    else {
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator
                .getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE);

        keyPairGenerator.initialize(new KeyGenParameterSpec.Builder(alias,
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                .setKeySize(2048)
                .setDigests(KeyProperties.DIGEST_SHA256)
                .build());

        KeyPair keyPair = keyPairGenerator.genKeyPair(); // 仅生成一次密钥对
        PublicKey pubKey = keyPair.getPublic();
        String publicKeyString = Base64.encodeToString(pubKey.getEncoded(), Base64.NO_WRAP);

        return pubKey;
    }
}

2. 修复Chrome扩展异步与变量问题

async function encryptUsingImportedKey(pubKey, data){
    const fromBase64 = buffer =>
        Uint8Array.from(atob(buffer), c => c.charCodeAt(0));

    const pubKeyArray = fromBase64(pubKey);

    const keyMaterial = await window.crypto.subtle.importKey(
        'spki',
        pubKeyArray,
        {name: 'RSA-OAEP', hash: 'SHA-256'},
        false,
        ['encrypt']
    );

    const encoder = new TextEncoder();
    const encoded_data = encoder.encode(data);
    const toBase64 = buffer =>
        btoa(String.fromCharCode(...new Uint8Array(buffer)));

    const encrypted_array = await window.crypto.subtle.encrypt(
        {name: "RSA-OAEP"},
        keyMaterial,
        encoded_data
    );

    const encrypted = toBase64(encrypted_array);
    return encrypted; // 返回加密结果
}       

3. 确保解密时Base64解码正确

Android端接收Chrome返回的Base64密文后,需用对应模式解码:

// 假设传入的encrypted是Chrome返回的Base64字符串
byte[] encryptedData = Base64.decode(encrypted, Base64.NO_WRAP);
// 再调用decryptData方法解密

验证要点

  • 对比数据库中存储的公钥与AndroidKeyStore中密钥对的公钥Base64值,确保一致。
  • 检查Chrome加密后的密文长度:2048位RSA OAEP加密后密文应为256字节,Base64编码后约344字符,长度不符则说明加密逻辑异常。

内容的提问来源于stack exchange,提问作者AmInO

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 12:05:21