Reputation: 420
I want to calculate RSA digital signature in Android using AndroidKeyStore. I have two solutions, the first is using java.security.Signature and the second using javax.crypto.Cipher. At the begin, I try to use Signature object and compute a signature successfully, but I have a problem. Signature object make a digest itself from my data so my first question is:
1- Is there any way to use Signature object by disabling calculation hash?
Then I chose the second solution (using Cipher object) by the code bellow:
// *** 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();
and using the key:
Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
inCipher.init(Cipher.ENCRYPT_MODE, privateKey);
but in "inCipher.init" function I get this error:
java.security.InvalidKeyException: Keystore operation failed
caused by: android.security.KeyStoreException: Incompatible purpose`
My second question is: 2- what is the problem? (I have to say, I can do encryption by public key but I can't do it by private key to calculate signature)
I encrypted a message with with private key without androidKeyStore and I succeeded. The code is in the below:
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);
Upvotes: 1
Views: 1788
Reputation: 49141
Regarding your first question:
If, as described in your comment, the message is already hashed (e.g. with SHA256) and only needs to be signed using PKCS#1 v1.5 padding, then this is possible with NONEwithRSA. However, it must be specified in the key properties that no digest is used.
If the signature should also comply with the standard, i.e. a verification with SHA256withRSA should be possible, the digest ID (more precisely, the DER encoding of the DigestInfo
value) must be placed in front of the hashed message. The following code (based on the posted code) shows this for SHA256 (tested for 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);
Adding the digest ID is necessary because according to RFC 8017, signing with PKCS#1 v1.5 uses RSASSA-PKCS1-v1_5 padding, which includes the digest ID.
Concerning your second question:
The simple formula "signing equals encryption with the private key" is only valid if no padding is used (textbook RSA), s. also 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.
When using private key encryption, the padding variant applied can vary depending on the library (if private key encryption is supported at all), which generally leads to incompatibilities. Probably to avoid such problems, the Android keystore may not support private key encryption (at least I haven't found a configuration that makes this possible).
The Java API as well as the Android API without keystore both support private key encryption. Therefore the last posted code works. Furthermore, for PKCS#1 v1.5 padding the variant RSASSA-PKCS1-v1_5 is used. If here the (e.g. with SHA256) hashed message would be passed and the ID of the digest would be placed in front of it, the generated signature could be verified with the algorithm SHA256withRSA (as in the posted code above).
Upvotes: 3