使用AndroidKeyStore计算数字签名
computing digital signature by using AndroidKeyStore
我想使用 AndroidKeyStore 在 Android 中计算 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- 问题是什么? (我不得不说,我可以用public密钥加密,但我不能用私钥计算签名)
我在没有 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);
关于您的第一个问题:
如果如评论中所述,消息已经 散列(例如使用 SHA256)并且只需要使用 PKCS#1 v1.5 填充进行签名,那么这NONEwithRSA 是可能的。但是,必须在关键属性中指定不使用摘要。
如果签名也应该符合标准,即可以使用 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. In practice, however, padding must always be applied for security reasons. For encryption and signing different paddings are involved: For encryption with PKCS#1 v1.5 padding the variant RSAES-PKCS1-v1_5 is applied and for signing with PKCS#1 v1.5 padding the variant RSASSA-PKCS1-v1_5.
使用 private 密钥加密时,应用的填充变体可能因库而异(如果完全支持私钥加密),这通常会导致不兼容。可能是为了避免此类问题,Android 密钥库可能不支持私钥加密(至少我还没有找到使这成为可能的配置)。
没有keystore的JavaAPI和AndroidAPI都支持私钥加密。因此,最后发布的代码有效。此外,对于 PKCS#1 v1.5 填充,使用变体 RSASSA-PKCS1-v1_5。如果此处传递(例如使用 SHA256)散列消息并将摘要的 ID 放在它前面,则可以使用算法 SHA256withRSA 验证生成的签名(如上面发布的代码)。
我想使用 AndroidKeyStore 在 Android 中计算 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- 问题是什么? (我不得不说,我可以用public密钥加密,但我不能用私钥计算签名)
我在没有 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);
关于您的第一个问题:
如果如评论中所述,消息已经 散列(例如使用 SHA256)并且只需要使用 PKCS#1 v1.5 填充进行签名,那么这NONEwithRSA 是可能的。但是,必须在关键属性中指定不使用摘要。
如果签名也应该符合标准,即可以使用 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. In practice, however, padding must always be applied for security reasons. For encryption and signing different paddings are involved: For encryption with PKCS#1 v1.5 padding the variant RSAES-PKCS1-v1_5 is applied and for signing with PKCS#1 v1.5 padding the variant RSASSA-PKCS1-v1_5.
使用 private 密钥加密时,应用的填充变体可能因库而异(如果完全支持私钥加密),这通常会导致不兼容。可能是为了避免此类问题,Android 密钥库可能不支持私钥加密(至少我还没有找到使这成为可能的配置)。
没有keystore的JavaAPI和AndroidAPI都支持私钥加密。因此,最后发布的代码有效。此外,对于 PKCS#1 v1.5 填充,使用变体 RSASSA-PKCS1-v1_5。如果此处传递(例如使用 SHA256)散列消息并将摘要的 ID 放在它前面,则可以使用算法 SHA256withRSA 验证生成的签名(如上面发布的代码)。