【问题标题】:computing digital signature by using AndroidKeyStore使用 AndroidKeyStore 计算数字签名
【发布时间】:2020-09-07 12:35:54
【问题描述】:

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

1-有没有办法通过禁用计算哈希来使用签名对象?

然后我通过下面的代码选择了第二种解决方案(使用 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, 0x01};
        byte[] result = inCipher.doFinal(x, 0, x.length);

【问题讨论】:

  • 通常签名是根据消息的哈希计算的。这是 99.99% 的用例所需要的。显然不支持称为“带有消息恢复的签名”的东西。这没关系,因为后一种技术只节省了少量的存储空间。如果您不使用 AndroidKeyStore,您的代码是否有效?
  • 感谢您的回答。在我们的架构中,消息被散列,但由另一个实体。我们收到摘要消息并直接签名。我测试了一个没有 androidKeyStore 的示例代码,它签名成功。我编辑了我的问题并添加了示例代码。

标签: android cryptography android-keystore


【解决方案1】:

关于你的第一个问题:

如果,如您的评论中所述,消息已经散列(例如使用 SHA256)并且只需要使用 PKCS#1 v1.5 填充进行签名,那么这可以使用 @987654321 @。但是,必须在关键属性中指定不使用摘要。
如果签名也应该符合标准,即应该可以使用 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, 0x01, 0x05, 0x00, 0x04, 0x20};
byte[] idHashedMessage = new byte[id.length + hashedMessage.length];
System.arraycopy(id, 0, idHashedMessage, 0, id.length);
System.arraycopy(hashedMessage, 0, idHashedMessage, 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 是必要的,因为根据 RFC 8017,使用 PKCS#1 v1.5 签名使用 RSASSA-PKCS1-v1_5 填充,其中包括摘要 ID。


关于你的第二个问题:

简单的公式“签名等于使用私钥加密”仅在不使用填充时才有效(教科书 RSA),s。还有here。然而,在实践中,出于安全原因,必须始终应用填充。对于加密和签名,涉及不同的填充:对于使用 PKCS#1 v1.5 进行加密,应用变量 RSAES-PKCS1-v1_5,对于使用 PKCS#1 v1.5 填充变量 RSASSA-PKCS1-v1_5 进行签名。
使用 private 密钥加密时,应用的填充变体可能会因库而异(如果完全支持私钥加密),这通常会导致不兼容。可能是为了避免此类问题,Android keystore 可能不支持私钥加密(至少我还没有找到可以做到这一点的配置)。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-08
    • 2011-12-25
    • 2020-11-30
    相关资源
    最近更新 更多