I'm playing around with adding some encryption to a Xamarin Forms app.
The project is targeting .Net standard 2.0, building for Android 11.0 - API30.
I'm using the PCLCrypto 2.1.40-Aplha nuget package and attempting to encrypt with the following code:
public static void AES_Encrypt(byte[] data, byte[] keyMaterial)
{
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var key = provider.CreateSymmetricKey(keyMaterial);
// The IV may be null, but supplying a random IV increases security.
// The IV is not a secret like the key is.
// You can transmit the IV (w/o encryption) alongside the ciphertext.
var iv = WinRTCrypto.CryptographicBuffer.GenerateRandom(provider.BlockLength);
byte[] cipherText = WinRTCrypto.CryptographicEngine.Encrypt(key, data, iv);
// When decrypting, use the same IV that was passed to encrypt.
//byte[] plainText = WinRTCrypto.CryptographicEngine.Decrypt(key, cipherText, iv);
}
Where both data and key-material are utf8 bytes of a string (so not-null terminated in the array).
My issue is the call to WinRTCrypto.CryptographicEngine.Encrypt is hanging and causing the app to hang.
Am I missing something with these calls ?
Edit 2 - This is occuring on every build version of PLCCrypto I have attempted so doesnt seem to be an alpha issue.
Related
I have the following code for Delphi 10.2 TurboPower LockBox 2 RSA keys generation and their represenation as some string:
//Object properties
object LbRSA1024: TLbRSA
PrimeTestIterations = 20
KeySize = aks1024
Left = 416
Top = 248
end
//Key generation - so simple!
LbRSA1024.GenerateKeyPair;
//Getting generated key as string
function TMainForm.GetPublicKey1024AsString: string;
var
str1, str2: TStringStream;
begin
Result:='';
if (LbRSA1024.PublicKey.Exponent.Int.dwUsed = 0)
or (LbRSA1024.PublicKey.Modulus.Int.dwUsed = 0) then
exit;
str1:= TStringStream.Create('');
str2:= TStringStream.Create('');
try
LbRSA1024.PublicKey.StoreToStream(str1);
str1.Position:=0;
//LbEncodeBase64(str1,str2);
TLbBase64.LbEncodeBase64(str1,str2);
Result:=str2.DataString;
finally
str1.Free;
str2.Free;
end;
end;
I got public key as ~200 character string (e.g. ...dh2dMTy/ab...) and I assigned his string to Android kotlin variable publicKeyString and tried to encrypt some other string with this public key generated from Delphi. Android Kotlin code is:
val publicKeyBytes: ByteArray = Base64.decode(publicKeyString, Base64.DEFAULT)
val X509PublicKey: X509EncodedKeySpec = X509EncodedKeySpec(publicKeyBytes)
val kf: KeyFactory = KeyFactory.getInstance("RSA")
val publicKey: PublicKey = kf.generatePublic(X509PublicKey)
val cipher: Cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val bytes = cipher.doFinal(s.toByteArray())
val result: String = String(bytes, Charsets.UTF_8)
Generally it is not working - the key is not accepted and the error messages are (I experimented with some variations of the code above):
Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic (:-1)
...
Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic (:-1)
at java.security.KeyFactory.generatePublic (:-1)
So, it may be possible that Delphi LockBox and Android/javax RSA has different key formats. So - I tried to do 2 things. First - I checked the code for Delphi key generation - specificaly - LbRsa.pas class procedure TRSA.GenerateRSAKeysEx(var PrivateKey, PublicKey : TLbRSAKey; KeySize : TLbAsymKeySize; PrimeTestIterations : Byte; Callback : TLbRSACallback); But this code is completely generic - large integers are generated and stored as the member variables and the then streamed to the strings with the code I have already provided above.
Then I tried to generate RSA keys in Android/javax and check whether they are the same as LockBox generated keys. I used the following code:
val REG_KEY: String = "REG_KEY"
val generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA /*, ANDROID_KEYSTORE */)
val builder = KeyGenParameterSpec.Builder(REG_KEY,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setKeySize(1024)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
//.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
generator.initialize(builder.build())
val keys = generator.generateKeyPair()
Log.i( "Cryptic", keys.private.toString())
Log.i( "Cryptic", keys.public.toString())
Log.i( "Cryptic", keys.private.encoded.contentToString())
Log.i( "Cryptic", keys.public.encoded.contentToString())
Log.i( "Cryptic", keys.private.encoded.toString(Charsets.UTF_8))
Log.i( "Cryptic", keys.public.encoded.toString(Charsets.UTF_8))
I em experiencing error message, because the private key is null, but the public key is generated:
java.lang.NullPointerException: keys.private.encoded must not be null
at com.batsoft.stockmobile.service.Cryptography.encryptString(Cryptography.kt:35)
I am still seeking to convert it to the string to look like LockBox generated key - just for comparison.
But at the present stage I am already confused - why I have to provide chaining mode and padding scheme for the key generation? My understanding is that keys are just encoded large integer. And the chaining mode, padding scheme are used for the encryption/decryption only? Of course, I need to provide key size, this is understandable.
So - my goal is to configure Android/javax RSA key generation and RSA key use so that it conform exactly to the RSA key generation and use in Delphi 10.2 LockBox 2. My aim is to use LockBox generated keys in the Android program. I have described the number of paths I have already taken but still I have not managed to generate javax keys in the same format as Delphi keys. As I don't know the exact configuration of they keys in LockBox (I guess - there is none, except for the key size) I can not configure my encryption/decryption on Android/javax as well.
How to achieve this conformity by changing Android/javax code?
Additional info: Delphi LbAsym.pas procedure TLbAsymmetricKey.StoreToStream(aStream : TStream); is very important, because it saves the key-integer into stream. In the case when passphrase is not used, the code is very straingtforward:
aStream.Write(KeyBuf, Len);
and the comment in the Pascal code reads as:
save key to ASN.1 format stream (encrypt if necessary)
So, maybe my question my be reformulated - how to save Android/javax generated keys in ASN.1 format or to read keys from ASN.1 format?
Additional Info 2: I am looking for the way to save Java-generated keys as PEM strings, and this can be done by the Android Kotlin code:
val generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA /*, ANDROID_KEYSTORE */)
val builder = KeyGenParameterSpec.Builder(REG_KEY,
KeyProperties.PURPOSE_ENCRYPT and KeyProperties.PURPOSE_DECRYPT)
.setKeySize(1024)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
//.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
generator.initialize(builder.build())
val keys = generator.generateKeyPair()
//https://stackoverflow.com/questions/25129822/export-rsa-public-key-to-pem-string-using-java
val writer = StringWriter()
val pemWriter = PemWriter(writer)
pemWriter.writeObject(PemObject("PUBLIC KEY", keys.public.encoded))
pemWriter.flush()
pemWriter.close()
Log.i( "Cryptic", writer.toString())
Note, this requires to add gradle dependencies:
implementation 'org.bouncycastle:bcpkix-jdk15to18:1.68'
implementation 'org.bouncycastle:bcprov-jdk15to18:1.68'
In order to use Delphi-generated keys (apparently LockBox 2 generates and saves them as PEM strings) I need to parse PEM strings and assign them as keys javax Cipher.
Additional Info 3: This is strange. Java generated string (from Additional Info 2) is a about 25 characters longer gan Delphi generated (based64 encoded) string (of course, I have removed initial and trailing strings that somes with PEM file and are just constants like '===PUBLIC KEY===') and it can be used perfectly as publicKeyString in my initial code - the encrytion with such public key works perfectly. Delphi public key string is some 25 characters shorter and does not work.
So - I have figure out what happens inside every step and tried to establish parallel steps, but ultimately I have failed to find solution.
Additional Info 4: Here are base64 encoded public keys:
Generated from Delphi:
MIGIAoGBALtEMVXxHBWzBx/AzO/aOHrYEQZB3VlqYBvqX/SHES7ehERXaCbUO5aEwyZcDrdh2dMTy/abNDaFJK4bEqghpC6yvCNvnTqjAz+bsD9UqS0w5CUh3KHwqhPv+HFGcF7rAuU9uoJcWXbTC9tUBEG7rdmdmMatIgL1Y4ebOACQHn1xAgIlKg==
Generated from Android Kotlin/java:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCuQi7gMZwWL1iEhNVgdu23S/rYYhtntXQlfVVBjcGiSE8EXzjjnZHxcYHcIszV0F6F20msGK8MFernJpWg8k7J3GLH4TYkQwEEy6jWnRdEB3uqQWFCNQ/CflCHtq1o1iSS0qmXcHQuI7zZ0cHd5FNDg4Bl/DveftEje9yTgUXN3wIDAQAB
I am not sure, but maybe there is some online service that can base64 decode those strings and then extract big integer from them according to some scheme and detecte the format.
As stated in the comments, the Delphi code generates a public key in PKCS#1 format, while the Kotlin code expects a key in X.509/SPKI format.
With BouncyCastle it is possible for the Kotlin code to import a PKCS#1 public key.
This requires the classes PEMParser and JcaPEMKeyConverter.
Example:
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.PEMParser
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import javax.crypto.Cipher
import java.security.PublicKey
import java.io.FileReader
import java.util.Base64
...
val inputFile: String = "<path to PKCS#1 PEM file>"
// Key import
var publicKey: PublicKey? = null
FileReader(inputFile).use { fileReader ->
PEMParser(fileReader).use { pemParser ->
val spki: SubjectPublicKeyInfo = pemParser.readObject() as SubjectPublicKeyInfo
val converter = JcaPEMKeyConverter()
converter.setProvider(BouncyCastleProvider())
publicKey = converter.getPublicKey(spki)
}
}
// Encryption
val cipher: Cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val ciphertext: ByteArray = cipher.doFinal("The quick brown fox jumps over the lazy dog".toByteArray())
val ciphertextB64: String = Base64.getEncoder().encodeToString(ciphertext);
println(ciphertextB64)
The code imports a PEM encoded public key in PKCS#1 format. The PEM encoding includes a format-specific header and footer, and in the body, there are line breaks after every 64 characters. The PEM encoded Delphi key is:
-----BEGIN RSA PUBLIC KEY-----
MIGIAoGBALtEMVXxHBWzBx/AzO/aOHrYEQZB3VlqYBvqX/SHES7ehERXaCbUO5aE
wyZcDrdh2dMTy/abNDaFJK4bEqghpC6yvCNvnTqjAz+bsD9UqS0w5CUh3KHwqhPv
+HFGcF7rAuU9uoJcWXbTC9tUBEG7rdmdmMatIgL1Y4ebOACQHn1xAgIlKg==
-----END RSA PUBLIC KEY-----
Unfortunately the import of your Delphi key does not work. There is a provider dependent error message displayed, e.g. in the case of the BouncyCastleProvider:
PEMException: unable to convert key pair: encoded key spec not recognized: RSA publicExponent is even
Indeed, the key generated by the Delphi code has an even public exponent with the value 9514 (0x252A):
0:d=0 hl=3 l= 136 cons: SEQUENCE
3:d=1 hl=3 l= 129 prim: INTEGER :BB443155F11C15B3071FC0CCEFDA387AD8110641DD596A601BEA5FF487112EDE8444576826D43B9684C3265C0EB761D9D313CBF69B34368524AE1B12A821A42EB2BC236F9D3AA3033F9BB03F54A92D30E42521DCA1F0AA13EFF87146705EEB02E53DBA825C5976D30BDB540441BBADD99D98C6AD2202F563879B3800901E7D71
135:d=1 hl=2 l= 2 prim: INTEGER :252A
This should not be the case (φ(n) or λ(n) and e would then not be coprime), s. here. You should therefore check the key generation in the Delphi code.
Another issue is in the line:
val result: String = String(bytes, Charsets.UTF_8)
The UTF-8 decoding of a ciphertext (which generally contains non-UTF-8 compliant byte sequences) corrupts this ciphertext.
If a ciphertext is to be converted to a string, a binary-to-text encoding such as Base64 must be applied.
Users can purchase a "Pro" version of my app. When they do, I store and verify their purchase as follows.
Combine the user's UUID and another unique string.
The resulting string is then encrypted using a static seed. I do this using SecureRandom.getInstance("SHA1PRNG", "Crypto")- This is the problem!
The resulting encrypted string is then the "unlock code".
Therefore, I always know the expected unique unlock code value for the user.
When the user purchases "Pro", I store the "unlock code" in the database.
I check to see whether the user has "Pro" by seeing if the stored "unlock code" in the database matches the expected code based on their unique info.
So, not the best system, but everything is obfuscated enough for my humble app.
The problem is that SecureRandom.getInstance("SHA1PRNG", "Crypto") fails on N because "Crypto" is not supported. I have learned that relying on specific providers is bad practice and Crypto is not supported on N. Oops.
So I have a problem: I rely on the encryption of a value-seed pair to always have the same output. Android N does not support the encryption provider I use, so I don't know how to ensure that the encryption output will be the same on N as it is on other devices.
My questions:
Is it possible to include "Crypto" in my APK so that it is always available?
Can I otherwise ensure the same output when encrypting a value-seed pair on Android N?
My code:
public static String encrypt(String seed, String cleartext) throws Exception {
byte[] rawKey = getRawKey(seed.getBytes(), seed);
byte[] result = encrypt(rawKey, cleartext.getBytes());
return toHex(result); // "unlock code" which must always be the same for the same seed and clearText accross android versions
}
private static byte[] getRawKey(byte[] seed, String seedStr) throws Exception {
SecureRandom sr;
sr = SecureRandom.getInstance("SHA1PRNG", "Crypto"); // what used to work
KeyGenerator kgen = KeyGenerator.getInstance("AES");
sr.setSeed(seed);
kgen.init(128, sr);
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
public static String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2 * buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
I had a discussion with the Android Security team about this recently.
In Android N, SHA1PRNG was removed because we don't have a secure implementation of it. Specifically, calling .setSeed(long) before requesting output from the PRNG replaces all of the entropy in the SecureRandom instance.
This behavior has long been pointed to as a security failure (read: frequently causes subtle bugs in apps), so we chose not to replicate it when the SecureRandom provider was replaced.
If you need a PRNG, then just use new SecureRandom().
That said... SecureRandom() is not designed to be used as a key derivation function, as you've done in your example. Please don't do this! Instead, use an algorithm such as PBKDF2, available via SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").
We've been warning developers about this for a while. Please see these posts:
Android Developers Blog: Using Cryptography to Store Credentials Safely
Android 4.2 broke my encrypt/decrypt code and the provided solutions don't work
IF YOU REALLY NEED SHA1PRNG, EVEN AFTER ALL OF THAT... then the workaround is to copy the implementation out of the Android source, like #artjom-b mentioned in his answer.
But please, only do this if you need compatibility while migrating to PBKDF2 or similar.
Using a PRNG such as SecureRandom for deriving data deterministically is generally a bad idea, because there is a history of breaking changes. It is always a good idea to use a specific implementation and include that with your app. It is possible to just copy the implementation code in your case.
SecureRandom.getInstance("SHA1PRNG", "Crypto"); looks up the "Crypto" provider which is org.apache.harmony.security.provider.crypto.CryptoProvider in Android 5.1.1. It redirects to org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl as the actual implementation. You can easily copy the code into your project under a different package and be sure to comply with the code license.
Then you can use it like this:
sr = new SecureRandom(new your.pkg.SHA1PRNG_SecureRandomImpl(), null);
The second provider argument is not used according to the code, but you can create a dummy provider.
The proper way to generate a key from some seed is to use a key derivation function (KDF). If seed is password-like, then PBKDF2 is a good KDF when a lot of iterations are specified. If seed is key-like, then a KBKDF like HKDF is recommended.
I added one class for CryptoProvider you can replace SecureRandom.getInstance("SHA1PRNG", "Crypto"); to SecureRandom.getInstance("SHA1PRNG", new CryptoProvider());
you can refer following link for solution, it working for me;
Security "Crypto" provider deprecated in Android N
I am creating a product whose firmware is updated using an android phone. The android application automatically downloads an encrypted version of the firmware, decrypts it, and sends it to the devices boot-loader. In order to generate the same secret key I specificy the password and salt in the code. I'm worried the apk will be decompiled and someone will be able to decrypt our firmware.
Is there a better way to decrypt/encrypt files or protect the code?
Code:
private byte[] DecryptFile(byte[] encryptedFileBuffer) {
final int iterationCount = 10;
byte[] dataDecrypted = null;
SecretKey secKey = null;
try {
byte[] salt = "salt1234".getBytes();
String accessThingy = "Password";
KeySpec keySpec = new PBEKeySpec(accessThingy.toCharArray(), salt, iterationCount);
secKey = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
Cipher desCipher;
// Create the cipher
desCipher = Cipher.getInstance(secKey.getAlgorithm());
desCipher.init(Cipher.DECRYPT_MODE, secKey,paramSpec);
dataDecrypted = desCipher.doFinal(encrptedFileBuffer);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
return dataDecrypted;
}
Yes and no.
No, if the decryption routine can be executed by an attacker (and why wouldn't it be) then the firmware would be compromised. The only way to avoid this is to add protection to the key on the device. You can think of OS/hardware support for this, or about storing the key/password outside of the device for instance. But a single compromised device would leak the firmware. This is the DRM conundrum.
And yes as you seem to use PBKDF1, MD5 and DES, none of which is particularly safe. MD5 is the most broken algorithm in that list, but it is the one that is least likely to actually become a problem. You should be using PBKDF2, SHA-2 and AES instead. Try this answer, Java 8 also has added support for PBKDF2 with SHA-2. Or you could actually use a fully random key instead of using password based encryption (PBE).
You may also want to consider asymmetric primitives (ECDSA/RSA) for encryption and code signing.
Would it be possible to move the decryption to the device itself? This way the code would be less accessible to the end user assuming that there wasn't any way to read back the program from the device (which would also cause an issue here).
I am programming an authentication service in Android and this one includes a server part written in java.
I do the same operations in both parts executing these two pieces of codes in Android and Server:
ANDROID:
String genChallengeResponse(String challenge, String message) {
String Hmac_ALG = "HmacSHA256";
SecretKey key = new SecretKeySpec(challenge.getBytes(), Hmac_ALG);
Mac m = Mac.getInstance(Hmac_ALG);
m.init(key);
m.update(password.getBytes());
byte[] mac = m.doFinal();
return new String(Base64.encode(mac, Base64.DEFAULT));
}
SERVER:
String genChallengeResponse(String challenge, String message) {
String Hmac_ALG = "HmacSHA256";
SecretKey key = new SecretKeySpec(challenge.getBytes(), Hmac_ALG);
Mac m = Mac.getInstance(Hmac_ALG);
m.init(key);
m.update(password.getBytes());
byte[] mac = m.doFinal();
return new String(Base64.encodeBase64(mac));
}
Starting from the same challenge and message these are the results:
Android: n2EaLpQr0uKgkZKhCQzwuIFeeLjzZKerZcETVNcfla4=
Server: n2EaLpQr0uKgkZKhCQzwuD9eeLjzZKerZcETVNcfla4=
^^
These are different just for TWO CHARACTERS.
The problem is that this strange behaviour does not appear in every pair of String passed to the functions...
I tried to use the UTF-8 in each system, but nothing changes...
Do someone knows what is the problem? If this is a known problem...
(is important to say that the problem is the same using Android 2.2 or also 4.0, then the problem is not the operating system, I think).
Can't comment yet therefore as answer:
I found out a few weeks ago that Android's Base64 uses different settings for the Linefeeds (check here: http://developer.android.com/reference/android/util/Base64.html )
I think in my case it was NO_WRAP missing.Perhaps one of the other options (NO_PADDING or URL-Safe, does the tested password contain + or - ?) could change your results...
I am developing an Android Application and I need to generate some RSA private and public keys to use for secure communication with web services. To do this I need to have the public key in a .NET compatible form.
Like:
<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>
So far I managed to to this:
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
keypair = keyGen.genKeyPair();
privateKey = keypair.getPrivate();
publicKey = keypair.getPublic();
// Get the bytes of the public and private keys
byte[] privateKeyBytes = privateKey.getEncoded();
byte[] publicKeyBytes = publicKey.getEncoded();
I've got no clue how to continue. Could you please provide some help ?
For anybody else interested, a very good tutorial can be found in here
http://www.codeproject.com/KB/security/porting_java_public_key.aspx?msg=3407475
If you need Base64 encoding/decoding, because it's not included in Android (at least in API 4) you could use the class from here: iharder.sourceforge.net/current/java/base64/
You don't show the type publicKey. If is not already, you should cast to an RSAPublicKey, then use the getPublicExponent() and getModulus() methods to extract the BigInteger. Then simply use standard Java IO, e.q. PrintStream.println() or printf() to generate the XML components.