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);
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).
Related
I want to mail encrypted log file from my app. Since logs can be larger I have encrypted data using AES and encrypted the key using RSA. Since the AES key is required to decrypt the log, I am sending the encrypted key and logs in the same file.
Question 1: Is this right approach ? If not what is the best approach to follow in this scenario.Below is the code for the same.
public static String encrypt(String data) {
StringBuilder encryptedData = new StringBuilder();
try {
// Generate AES key.
KeyGenerator generator = KeyGenerator.getInstance("AES");
// The AES key size in number of bits.
generator.init(256);
SecretKey secKey = generator.generateKey();
// Initialize AES Cipher, IV and encrypt string.
Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
aesCipher.init(Cipher.ENCRYPT_MODE, secKey, new IvParameterSpec(new byte[16]));
byte[] byteCipherText = aesCipher.doFinal(data.getBytes());
String encryptedText = Base64.encodeToString(byteCipherText, Base64.DEFAULT);
// Initialize RSA Cipher and generate public key.
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(Base64.decode(PUBLIC_KEY, Base64.DEFAULT));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey puKey = keyFactory.generatePublic(publicSpec);
cipher.init(Cipher.PUBLIC_KEY, puKey);
// Encrypt key and text.
byte[] encryptedKey = cipher.doFinal(secKey.getEncoded());
String aesKey = Base64.encodeToString(encryptedKey, Base64.DEFAULT);
encryptedData.append(aesKey);
encryptedData.append(encryptedText);
} catch (Exception e) {
e.printStackTrace();
}
return encryptedData.toString();
}
Since the AES key is required to decrypt the log, I am sending the encrypted key and logs in the same file.
Question 1: Is this right approach ? If not what is the best approach to follow in this scenario.Below is the code for the same.
The approach is correct, what I'm missing is authentication (HMAC, GCM, ...).
There are some standards how to bundle the encrypted key, content and authentication together (e.g. CMS, PKCS7, OpenPGP, ..) however if it's for your own application, you may do it your way (don't bother with standards).
If you want to use RSA use RSA-KEM
Well, using RSA KEM you may save a little of performance skipping the padding, but I'd try if it is feasible for you. As well there's an issue when encrypting the same key material with different public keys.
I'd keep it simple - just use the properly padded RSA encryption.
I'd suggest to use OAEP padding RSA/ECB/OAEPWithSHA-256AndMGF1Padding instead of PKCS1Padding (OAEP is considered newer/safer)
From API level 28, Google has restricted Security provider feature(bouncy castle issue).
So alternatively we have added Security provider using spongy castle
Now we can able to generate a keypair. But the key pair is not matching with the previous one. We can't get Private keyThis is we used previously, Old codeapi 27:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", "BC");
SecureRandom random =SecureRandom.getInstance("SHA1PRNG");
keyGen.initialize(256, random);KeyFactory kaif = KeyFactory.getInstance("EC", "BC");
KeyPair pair = keyGen.generateKeyPair();
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();
After the API level issue, we have removed "BC" and added Bouncy Castle manually by adding the below lineSecurity.insertProviderAt(BouncyCastleProvider(), 1);
by implementing Bouncy castle in dependencies,
implementation "com.madgag.spongycastle:core:1.58.0.0"
implementation "com.madgag.spongycastle:prov:1.58.0.0"
But the key pair is not matching with the previous one.
New Code:api 28
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
keyGen.initialize(256, random);
KeyFactory kaif = KeyFactory.getInstance("EC");
KeyPair pair = keyGen.generateKeyPair();
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();
But the key pair is not matching with the previous one.
Image:
try insert new BouncyCastleProvider() on the first row of your security provider and remove all setprovider("BC") from your code.
Well, I was about to encrypt a file from SDcard on user's order and Of course decrypt it back after that. well after a bit of searching I found out how to do it in android using "AES". everything was fine until users give me feedback about decryption Failure on Android Nougat! I checked and Unfortunately they were right :(
App was doing fine on Lollipop and Kitkat but not "NOUGAT"!
I checked the Log (as the first thing to do which comes to mind) and I saw this:
New versions of the Android SDK no longer support the Crypto provider.
If your app was relying on setSeed() to derive keys from strings, you
should switch to using SecretKeySpec to load raw key bytes directly OR
use a real key derivation function (KDF). See advice here :
http://android-developers.blogspot.com/2016/06/security-crypto-provider-deprecated-in.html
And I kind of freaked out because I was exactly using Crypto-Provider!!!
public static byte[] generateKey(String password) throws Exception {
byte[] keyStart = password.getBytes("UTF-8");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("[SHA1PRNG][1]", "Crypto");
random.setSeed(keyStart);
kgen.init(128, random);
SecretKey skey = kgen.generateKey();
return skey.getEncoded();
}
Then I find some Talks about this on SO: [1], [2]
developer site says we should no longer use crypto-provider for generation of Key for AES because:
This provider only provided an implementation of the algorithm
“SHA1PRNG” for instances of SecureRandom. The problem is that the
SHA1PRNG algorithm is not cryptographically strong.
and instead we should generate key like this:
public byte[] generateKeyForAES(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
/* Store these things on disk used to derive key later: */
int iterationCount = 1000;
int saltLength = 32; // bytes; should be the same size as the output (256 / 8 = 32)
int keyLength = 256; // 256-bits for AES-256, 128-bits for AES-128, etc
/* When first creating the key, obtain a salt with this: */
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
random.nextBytes(salt);
/* Use this to derive the key from the password: */
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
return key.getEncoded();
}
As a good boy I replaced new way of generating key but it didn't work!!! (bad google) then I start to take a good look at this code and I realized that because every time we run this method, "salt" is generating "RANDOM"ly and we couldn't generate a same key twice. So you either save salt! after first time it is generated Or use an static salt. well I used an static salt :)
byte[] salt = {(byte) 0xA4, (byte) 0x0B, (byte) 0xC8, (byte) 0x34, (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13};
As you may guess, Yes it did NOT worked either! what I mean is it did decrypt the file but it was corrupted! NOT the same file was retrieved!
It's been a WEEK since I face this problem and gone through a LOT of way to solve it but none of them work.
There are a lot of Talks on SO which is about "being Unable to decrypt via AES" but there were either suggesting a way that didn't work or were irrelevant!
some says its a problem from Transformation Algorithm in initializing Cipher after generating key and instead of this:
Cipher cipher = Cipher.getInstance("AES");
we should do it like:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
but after that decryption even Fails to Decrypt from the beginning and it is worse than decrypting some corrupted file (with the same size of course)!
After I became really exhausted I think that forget about all Security risks and just make it work!!!
Then as #artjom-b say in his ANSWER I tried to import CryptoProvider class locally in my project and then even if Android delete it completely, it will work in my project!!!
I just copy CryptoProvider, SHA1PRNG_SecureRandomImpl and of course all related class and put it in a package in my source code, then
,back to earlier way that I use to generate key for AES, I change generate key method as follows:
public static byte[] generateKey(String password) throws Exception {
byte[] keyStart = password.getBytes("UTF-8");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", new CryptoProvider());
random.setSeed(keyStart);
kgen.init(128, random);
SecretKey skey = kgen.generateKey();
return skey.getEncoded();
}
As you may noticed the way SucureRandom is being initialized is changed! and insted of "crypto" I reference to my local provider and turn it like:
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", new CryptoProvider());
As the Serial of my failures in getting a Simple encryption on Android N to work follows, again it didn't work. I mean it did decrypting but the file was Corrupted again!
I really really appretiate any Help and I wonder If anyone face a same problem and knows how to fix it!?
I am trying to store an AES key in the Android KeyStore using following code:
SecretKey AESkey = new SecretKeySpec(
byteKey, 0, byteKey.length, "AES/CBC/PKCS5Padding");
if (ks == null)
{
ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
}
ks.deleteEntry("aes_key");
ks.setEntry("aes_key",
new KeyStore.SecretKeyEntry(AESkey),
new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
The line with 'setEntry(...)' fails throwig:
java.security.KeyStoreException: java.lang.IllegalArgumentException: Unsupported secret key algorithm: AES/CBC/PKCS5Padding
How can I store my key in the Android.KeyStore?
CBC and PKCS5Padding are not part of a key but key size is.
Somewhat guessing given the error message just use "AES".
SecretKey AESkey = new SecretKeySpec(byteKey, 0, byteKey.length, "AES");
The documentation is thin at best and the closest I can find is SecretKeyFactory Algorithms: "AES" Constructs secret keys for use with the AES algorithm. See: SecretKeyFactory Algorithms.
String text = name1.getText().toString();
// Sending side
byte[] data = null;
try {
data = text.getBytes("UTF-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
String base64 = Base64.encodeToString(data, Base64.DEFAULT);
was able to encrypt password and will to decrypt the same password but i have something in mind that im not sure of this is my first time trying to encrypt a password. Is it safe to encrypt the password this way because I tried encrypt a password : zxc and the result is just a four letter password (its result is : enhj) so im wondering if it is a safe way to encrypt the password. Any ideas on how to remake the code to make it safer and not easy to decode and ideas on how to decrypt the encrypted password?
UPDATE: This is a sample of encryption and decryption I found at this site here but I cant make it run.
encryption
String password = "password";
int iterationCount = 1000;
int keyLength = 256;
int saltLength = keyLength / 8; // same size as key output
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
randomb.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize());
random.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes("UTF-8"));
decryption
String[] fields = ciphertext.split("]");
byte[] salt = fromBase64(fields[0]);
byte[] iv = fromBase64(fields[1]);
byte[] cipherBytes = fromBase64(fields[2]);
// as above
SecretKey key = deriveKeyPbkdf2(salt, password);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivParams);
byte[] plaintext = cipher.doFinal(cipherBytes);
String plainrStr = new String(plaintext , "UTF-8");
You've tagged this cryptography, passwords, and encryption, so I'll answer it as such.
First, Base64 is not actually encryption, it's merely encoding - essentially changing from 8 bit bytes to 6 bit bytes, and your test is perfect - 3*8 bit characters = 24 bits. 24bits/6bits = 4 Base64 characters. I've also verified that enhj is indeed the Base64 encoding of zxc on my own C implementation of Base64. For further evidence of this, note that you didn't provide any encryption key!
Second, for user authentication (which is what I assume you're doing), do not encrypt passwords - that's a major blunder Adobe just made. For user authentication, you don't ever need to see the user's password again - you merely need to verify that they entered the same thing they did before. Thus, when they enter a password the first time, you salt and hash it. The next time, you retrieve the salt you used the first time, and hash the freshly entered password with the same salt (and # of iterations/work factor) - if the result is the same as you have on record, let them in, since giving the same password will get the same result.
The three canonical answers to How to securely hash passwords? are PBKDF2, Bcrypt, and Scrypt. A quick Google search regarding Android password hashing turned up:
How can I make sure password hashing is secure on computers while not being prohibitively slow on mobile devices? and safe to use jBCrypt and recommend it to my organization? which refer to the mindrot jBCrypt Java library and/or the Spring Security variant of jBCrypt
PBKDF2 with SHA256 on android refers to a SpongyCastle 1.47+ implementation of PBKDF2-HMAC-SHA-256 as well references to PBKDF2-HMAC-SHA-1.
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest());
generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password), salt, iterations);
KeyParameter key = (KeyParameter)generator.generateDerivedMacParameters(keySizeInBits);
The Android-developers blogspot article Using Cryptography to Store Credentials Safely also references PBKDF2-HMAC-SHA-1.
public static SecretKey generateKey(char[] passphraseOrPin, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Number of PBKDF2 hardening rounds to use. Larger values increase
// computation time. You should select a value that causes computation
// to take >100ms.
final int iterations = 8000;
// Generate a 160-bit key
final int outputKeyLength = 160;
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey;
}
In ALL cases, choose as high an iteration count/work factor as you can stand the delay of (using as fast a library for your chosen algorithm as you can abide by the license of). Your salt should be a cryptographically random series of bytes in the 8 to 16 byte length range.
For PBKDF2 in particular, never use more outputBytes than the native hash size or you give an attacker a comparative advantage - SHA-1's native size is 20 bytes, SHA-256 is 32 bytes, and SHA-512 is 64 bytes natively.
If you really do need encryption rather than authentication, the "Using Cryptography to Store Credentials Safely" link above covers that too, though the better answer is to store the salt and number of iterations/work factor and simply regenerate the key from the password each time - if the data decrypts, it was good. If not, well, bad password.
You are not encrypting anything. You are converting bytes to base64 encoding. You need to use a ciphering algorithm. See http://examples.javacodegeeks.com/core-java/security/simple-symmetric-key-encrypt-decrypt/