使用AndroidKeyStore

问题描述

我想使用AndroidKeyStore在Android中计算RSA数字签名。我有两个解决方案,第一个使用java.security.Signature,第二个使用javax.crypto.Cipher。首先,我尝试使用Signature对象并成功计算签名,但是我遇到了问题。签名对象根据我的数据进行摘要,所以我的第一个问题是:

1-是否可以通过禁用计算哈希来使用Signature对象?

然后我通过下面的代码选择了第二个解决方案(使用Cipher对象):

// *** Creating Key
            KeyPairGenerator spec = KeyPairGenerator.getInstance(
                    // *** Specified algorithm here
                    // *** Specified: Purpose of key here
                    KeyProperties.KEY_ALGORITHM_RSA,"AndroidKeyStore");
            spec.initialize(new KeyGenParameterSpec.Builder(
                    alias,KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) //  RSA/ECB/PKCS1Padding
                    .setKeySize(2048)
                    // *** Replaced: setStartDate
                    .setKeyValidityStart(notBefore.getTime())
                    // *** Replaced: setEndDate
                    .setKeyValidityEnd(notAfter.getTime())
                    // *** Replaced: setSubject
                    .setCertificateSubject(new X500Principal("CN=test"))
                    // *** Replaced: setSerialNumber
                    .setCertificateSerialNumber(BigInteger.ONE)
                    .build());
            KeyPair keyPair = spec.generateKeyPair();

并使用密钥:

Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding","AndroidKeyStoreBCWorkaround");
inCipher.init(Cipher.ENCRYPT_MODE,privateKey);

但是在“ inCipher.init”函数中,出现此错误:

java.security.InvalidKeyException: Keystore operation failed
caused by: android.security.KeyStoreException: Incompatible purpose`

我的第二个问题是:2-问题是什么? (我必须说,我可以通过公钥进行加密,但不能通过私钥进行加密来计算签名)

我使用不带androidKeyStore的私钥加密了一条消息,并成功。代码在下面:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(2048);
        KeyPair kp = kpg.generateKeyPair();
        PublicKey pub = kp.getPublic();
        PrivateKey pvt = kp.getPrivate();

        Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        inCipher.init(Cipher.ENCRYPT_MODE,pvt);
        byte[] x = new byte[]{0x01,0x01,0x01};
        byte[] result = inCipher.doFinal(x,x.length);

解决方法

关于第一个问题:

如果如您评论中所述,消息已被散列(例如,使用SHA256)并且仅需要使用PKCS#1 v1.5填充进行签名,则可以使用{{ 3}}。但是,必须在关键属性中指定不使用摘要。
如果签名也应符合标准,即应该可以使用SHA256withRSA进行验证,则摘要ID(更确切地说是DigestInfo值的DER编码)必须放在散列消息的前面。以下代码(基于发布的代码)针对SHA256(针对Android P / API 28测试)显示了此代码:

// Load keystore
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

// Create key (if not already in keystore)
String alias = "Some Alias";
if (!keyStore.containsAlias(alias)) {

    Calendar notBefore = Calendar.getInstance();
    Calendar notAfter = Calendar.getInstance();
    notAfter.add(Calendar.YEAR,1);

    KeyPairGenerator spec = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA,"AndroidKeyStore");
    spec.initialize(new KeyGenParameterSpec.Builder(alias,KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)              // for signing / verifying
            .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)        // use RSASSA-PKCS1-v1_5
            .setDigests(KeyProperties.DIGEST_NONE)                                  // apply no digest
            .setKeySize(2048)
            .setKeyValidityStart(notBefore.getTime())
            .setKeyValidityEnd(notAfter.getTime())
            .setCertificateSubject(new X500Principal("CN=test"))
            .setCertificateSerialNumber(BigInteger.ONE)
            .build());

    spec.generateKeyPair();
}

// Retrieve key
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias,null);
PrivateKey privateKey = privateKeyEntry.getPrivateKey();
PublicKey publicKey = privateKeyEntry.getCertificate().getPublicKey();

// Hash message (hashedMessage corresponds to your message)
MessageDigest digest = MessageDigest.getInstance("SHA-256");                        // SHA256 as digest assumed
byte[] message = "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8);
byte[] hashedMessage = digest.digest(message);

// Concatenate ID (in this example for SHA256) and message in this order
byte[] id = new byte[]{0x30,0x31,0x30,0x0d,0x06,0x09,0x60,(byte) 0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x05,0x00,0x20};
byte[] idHashedMessage = new byte[id.length + hashedMessage.length];
System.arraycopy(id,idHashedMessage,id.length);
System.arraycopy(hashedMessage,id.length,hashedMessage.length);

// Sign with NONEwithRSA
Signature signing = Signature.getInstance("NONEwithRSA");
signing.initSign(privateKey);
signing.update(idHashedMessage);
byte[] signature = signing.sign();

// Verify with SHA256withRSA
Signature verifying = Signature.getInstance("SHA256withRSA");                       // Apply algorithm that corresponds to the digest used,here SHA256withRSA
verifying.initVerify(publicKey);
verifying.update(message);
boolean verified = verifying.verify(signature);
System.out.println("Verification: " + verified);

添加摘要ID是必要的,因为根据NONEwithRSA,使用PKCS#1 v1.5进行签名时会使用RSASSA-PKCS1-v1_5填充,其中包括摘要ID。


关于第二个问题:

简单公式“使用私钥进行签名等于加密”仅在不使用填充的情况下有效(教科书RSA),s。还有RFC 8017。但是,实际上,出于安全原因,必须始终应用填充。对于加密和签名,需要使用不同的填充:对于使用PKCS#1 v1.5填充的加密,将应用变量here;对于使用PKCS#1 v1.5填充的签名,将使用变量RSAES-PKCS1-v1_5
使用私有密钥加密时,所应用的填充变量可能会因库而异(如果完全支持私有密钥加密),这通常会导致不兼容。可能是为了避免此类问题,Android密钥库可能不支持私钥加密(至少我没有找到使之成为可能的配置)。

不带密钥库的Java API和Android API均支持私钥加密。因此,最后发布的代码有效。此外,对于PKCS#1 v1.5填充,使用变体RSASSA-PKCS1-v1_5。如果此处传递了(例如,带有SHA256的)散列消息并将摘要的ID放在其前面,则可以使用算法SHA256withRSA验证生成的签名(如上面的发布代码所示)。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...