Problem Description:
I am working on an android app that will generate an AES key and store it in keystore.
Whenever I need to send data to a server, I use the AES key to encrypt data, use server public key to encrypt the AES key and send encrypted data + encrypted key to the server to decrypt the full load.
the code to generate the Key in android is below:
public void generateKey()
{
try {
if (!keyStore.containsAlias(KEY_ALIAS)) {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore);
keyGenerator.init(
new KeyGenParameterSpec.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false)
.build());
keyGenerator.generateKey();
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
When I need to encrypt data i fetch the key using this function
private java.security.Key getSecretKey(Context context) throws Exception {
return keyStore.getKey(KEY_ALIAS, null);
}
Using this key I was able to encrypt the data. but the issue is trying to encrypt the key to send it to the server.
I tried to get the key as byte [] to encrypt it but using the function
key.getEncoded();
the resulting byte array is always null.
What is wrong here and how to solve it?
App is for Android 23+
I'm not sure, but you didn't save the key before you do the encryption, make sure you have it on android keystore before.
This kotlin example would help:
#Throws(IOException::class, KeyStoreException::class, CertificateException::class, NoSuchAlgorithmException::class)
fun saveKeyPair(pair: KeyPair, storeAlias: String) {
val certificate = generateCertificate(pair)
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore!!.load(null, null)
val x509CertificateObject = X509CertificateObject(certificate)
keyStore.setKeyEntry(storeAlias, pair!!.private, null, arrayOf<java.security.cert.Certificate?>(x509CertificateObject))
}
Hope that could help.
Related
This question already has answers here:
Converting Secret Key into a String and Vice Versa
(6 answers)
Converted SecretKey into bytes, how to convert it back to a SecretKey?
(2 answers)
Closed 2 years ago.
What I am trying to do is encrypt a string into a byte[] with javax.crypto, send it through a DatagramSocket, then on the receiving side, decrypt it.
public static final String UNICODE = "UTF-8";
private SecretKey key;
private Cipher cipher;
public StringHandler() {
try {
key = generateKey("AES");
cipher = Cipher.getInstance("AES");
} catch (Exception e) {
e.printStackTrace();
}
}
private SecretKey generateKey(String type) throws Exception {
KeyGenerator gen = KeyGenerator.getInstance(type);
SecretKey key = gen.generateKey();
return key;
}
public byte[] encrypt(String msg) {
try {
byte[] data = msg.getBytes(UNICODE);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public String decrypt(byte[] data) {
try {
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(data), UNICODE);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
I read on a website how to encrypt and decrypt data, and I wrote this class. From what I can tell, the security key has to be the same on both sides for it to decrypt properly. Is there any way to convert it to a string or something, then get it from a string on the server side? Currently I have no idea how decrypt it on a different program.
You can turn a key represented as an array of bytes into a form that can be sent as "text". Base64 encoding is a common way to do that.
But that doesn't solve your real problem:
Is there any way to convert it to a string or something, then get it from a string on the server side?
The real problem is how to send the string that represents to secret key to the server securely; i.e. without someone else being able to steal the key while it is in transit.
And the answer is that you can't ... without using another encryption mechanism:
One possibility to encrypt the secret key with a different secret key that both the client and server already know.
A second possibility is to use public key encryption.
A third possibility is to use a secure communication system to transmit the secret key. For example a network based on quantum cryptography.
This is far too large a topic to cover here. If you want to understand the "key distribution problem" and its solutions, find a good textbook. Or start with these Wikipedia articles:
https://en.wikipedia.org/wiki/Cryptography
https://en.wikipedia.org/wiki/Public-key_cryptography
https://en.wikipedia.org/wiki/Quantum_key_distribution
In my Android app, I need a way to encrypt the data I store in a local DB.
I chose Realm DB because the offer a seamless integration with encryption. I just need to pass a key when initializing the Realm instance. This key must be of 64 byte size.
For security reason, I found out that the best way to store this key is in AndroidKeyStore. I'm struggling to find a way to generate a key (using any algorithm) with that size, and getting it into a 64-byte array. I'm trying to keep a minSdk of API 19, but I believe I can bump it up to 23 if needed (many changes to AndroidKeyStore between these two versions).
Does anyone have an idea? Here is my code:
Class Encryption.java
private static KeyStore ks = null;
private static String ALIAS = "com.oi.pap";
public static byte[] loadkey(Context context) {
byte[] content = new byte[64];
try {
if (ks == null) {
createNewKeys(context);
}
ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
content= ks.getCertificate(ALIAS).getEncoded(); //<----- HERE, I GET SIZE GREATER THAN 64
Log.e(TAG, "original key :" + Arrays.toString(content));
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = Arrays.copyOfRange(content, 0, 64); //<---- I would like to remove this part.
return content;
}
private static void createNewKeys(Context context) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
try {
// Create new key if needed
if (!ks.containsAlias(ALIAS)) {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(ALIAS)
.setSubject(new X500Principal("CN=PapRealmKey, O=oipap"))
.setSerialNumber(BigInteger.ONE)
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.setKeySize(256)
.setKeyType(KeyProperties.KEY_ALGORITHM_EC)
.build();
KeyPairGenerator generator = KeyPairGenerator
.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
Log.e(TAG, "generated key :" + Arrays.toString(keyPair.getPrivate().getEncoded()));
}
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
The point of AndroidKeyStore is to move sensitive key material out of your app, out of the operating system and into secure hardware where it can never leak or be compromised. So, by design, if you create a key in AndroidKeyStore, you can never get the key material out.
In this case, Realm DB wants the secret key material, so you can't give it an AndroidKeyStore key. Also, what Realm wants is two AES keys, not an EC key, as you were trying to generate.
The right way to generate the key material you need is:
byte[] dbKey = new byte[64];
Random random = new SecureRandom();
random.nextBytes(dbKey);
// Pass dbKey to Realm DB...
Arrays.fill(dbKey, 0); // Wipe key after use.
Just 64 random bytes. However, you're going to need to store those bytes somewhere. You could create an AES key with AndroidKeyStore and use it to encrypt dbKey. Something like:
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(
new KeyGenParameterSpec.Builder("dbKeyWrappingKey",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
SecretKey key = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV();
byte[] encryptedDbKey = cipher.doFinal(dbKey);
You'll need to save both iv and encryptedDbKey somewhere (not in the database!) so that you can recover dbKey. Then you can decrypt it with:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
key = (SecretKey) keyStore.getKey("dbKeyWrappingKey", null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] dbKey = cipher.doFinal(encryptedDbKey);
// Pass dbKey to Realm DB and then wipe it.
However, with all of that said... I don't think you should do any of it. I don't think this actually gives you any security that Android doesn't give you by default anyway. If an attacker tries to dump the device storage, which contains your database, he'll get nothing because Android encrypts all of the storage anyway. If an attacker can root the device, he can run code as your app and use it to decrypt dbKey the same way your app does.
Where AndroidKeyStore may really add value is if you add some additional protections on dbKeyWrappingKey. For example, if you set it to require user authentication within, say five minutes, it will only be possible to use dbWrappingKey to decrypt dbKey when the user is around to enter their PIN/pattern/password or touch the fingerprint scanner. Note that this only works if the user has a PIN/pattern/password, but if they don't, well, your database is wide open to anyone who picks up the phone anyway.
See KeyGenParameterSpec for all of the things you can do to restrict the ways dbKeyWrappingKey can be used.
As far as I know, the usual way to solve this problem is, that you generate your own random key of the size you need (master-key), and this master-key can be encrypted with the help of the key store.
Generate your own random master-key of the size you need.
Encrypt data with this master-key (e.g. symmetric encryption).
Encrypt the master-key with the help of the key-store.
Store the encrypted master-key somewhere.
To decrypt your data:
Read the encrypted master-key.
Decrypt the master-key with the help of the key-store.
Decrypt data with the master-key.
In other words, it is not the master-key which is stored inside the key-store, but the key-store can be used to protect/encrypt your master-key.
EDIT: The second question is no longer needed. The problem was a result of padding issues; changing the parameters of the Cipher to "RSA/ECB/PKCS1Padding" fixed it, which was already implemented in the other Ciphers used.
For a school assignment I have to create a two programs in Java (a client and server) that use RSA asymmetric encryption to create and agree on a session key that uses DES encryption. The final message in the exchange contains a key that has been generated by the client, which is encrypted by the client's private key, and then encrypted again using the server's public key. The server can then decrypt the message using its private key and then decrypt it again with the client's public key to obtain the DES key. However, the first encryption results in a byte array of size 256, and the second encryption requires a byte array that is smaller than that. Is there a way to manipulate the data such that I can encrypt the key twice, as specified in the assignment? Note that this is a requirement, as is using the DES algorithm for the session key.
Additionally, disregarding the second encryption such that the next part of the assignment, which allows the client and server to send encrypted messages to one another, produces another problem. Currently, the client wraps the key using the server's public key, and then the server unwraps it using its private key. However, unwrapping the session key on the server side produces an InvalidKeyException; the unwrapped key length is 256 bytes, which is completely wrong.
On the server side, I have:
byte[] m4 = new byte[256];
datIn.read(m4);
cUwp.init(Cipher.UNWRAP_MODE, myKey);
ks = (SecretKey)cUwp.unwrap(m4, "DES", Cipher.SECRET_KEY);
System.out.println("Recieved key:\n" + ks);
try{
Cipher desCipher = Cipher.getInstance("DES");
desCipher.init(Cipher.DECRYPT_MODE, ks);
}
catch(NoSuchPaddingException|InvalidKeyException e){
System.out.println("Error: " + e);
}
On the client side:
KeyGenerator keygen = KeyGenerator.getInstance("DES");
SecretKey key = keygen.generateKey();
cWrp.init(Cipher.WRAP_MODE, theirKey);
byte[] m4 = cWrp.wrap(key);
datOut.write(m4);
ks = key;
try{
Cipher desCipher = Cipher.getInstance("DES");
desCipher.init(Cipher.DECRYPT_MODE, ks);
}
catch(NoSuchPaddingException|InvalidKeyException e){
System.out.println("Error: " + e);
}
I am not getting any size discrepancies in any other parts of my code; any other decrypted messages are the proper size, but none of them use the wrap() method, as they are Strings and not SecretKeys. Is there something that I'm missing?
You're doing this completely wrong. The encryption must be done with the public key, and the decryption by the private key. Otherwise anybody can decrypt it.
Throw it all away and use TLS.
Does somebody know how I can create an RSA key in C++ from an encoded byte array?
My problem is that I try to develop a C++ client that is interacting with a server which is coded in Java.
Well in Java the client receives the rsa key encoded as an byte array, decodes it to a RSA RSAPublicKey and encrypts a message with this key.
The java server/client code:
public static PublicKey decodePublicKey(byte[] p_75896_0_)
{
try
{
X509EncodedKeySpec var1 = new X509EncodedKeySpec(p_75896_0_);
KeyFactory var2 = KeyFactory.getInstance("RSA");
return var2.generatePublic(var1);
}
catch (NoSuchAlgorithmException var3)
{
;
}
catch (InvalidKeySpecException var4)
{
;
}
field_180198_a.error("Public key reconstitute failed!");
return null;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
this.publicKey = CryptManager.decodePublicKey(data.readByteArray());
After that the client is doing some encrypting stuff with his key.
The key gets sent like this:
public static final KeyPair keys;
static
{
try
{
KeyPairGenerator generator = KeyPairGenerator.getInstance( "RSA" );
generator.initialize( 1024 );
keys = generator.generateKeyPair();
} catch ( NoSuchAlgorithmException ex )
{
throw new ExceptionInInitializerError( ex );
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
byte[] pubKey = keys.getPublic().getEncoded();
writeBytes(pubKey);
My problem is how to get the key from the byte array in C++.
Update:
Im currently working on this code:
char* publicKey = ...
int publicKeyLength = 162;
EVP_PKEY* key = EVP_PKEY_new();
if(d2i_PUBKEY(&key, (const unsigned char**) &publicKey, publicKeyLength) != 0){
logError("Problem!");
}
logMessage("Key: "+to_string((uint64_t) (void*) key));
Well my problem now is that i have an SIGSEGV error on the third line and dont know what this course. Well the key should be valid.
What Java returns for the public key is a SubjectPublicKeyInfo structure, which doesn't just contain the (PKCS#1 encoded) values for the public key, but also the key identifier etc.
So to decode this you have to type "decode SubjectPublicKeyInfo openssl" in your favorite search engine. Then you'll find (after some scrolling) the following information from here:
d2i_PUBKEY() and i2d_PUBKEY() decode and encode an EVP_PKEY structure
using SubjectPublicKeyInfo format. They otherwise follow the conventions
of other ASN.1 functions such as d2i_X509().
Obviously you'd need the decoding algorithm.
Note that openssl is C so beware of buffer overruns when decoding stuff. I'd rather have a 1024 bit RSA key that is used with secure software than a 2048 bit key with software full of buffer overruns.
Needless to say you need to trust the public key before importing it. There is a reason why it is called the public key infrastructure (PKI).
I am trying to do client side encryption for the data I am sending to S3. I want to take encryption keys as input from the user. In what format should I take the key from the user.
I tried to take input as the private key generated by ssh-keygen and tried reading it using the code mentioned at Get public key from private in Java. But I get the following error
Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:217)
at java.security.KeyFactory.generatePrivate(KeyFactory.java:372)
I generated the key using ssh-keygen -t rsa
I want to take encryption keys as input from the user
I does not try your above need but I have generated 256bit Secret key to encrypt and decrypt my data in S3.
// Code To Generate Secret Key.
KeyGenerator symKeyGenerator = KeyGenerator.getInstance("AES");
symKeyGenerator.init(256);
SecretKey symKey = symKeyGenerator.generateKey();
System.out.println(new String(Base64.encodeBase64(symKey.getEncoded())));
And I used like this to Download and upload objects in S3
// Code To Make Objects Encrypt while uploading and Decrypt while Downloading.
public static void s3WithEncryption(AWSCredentials credentials) {
String myKeyPair = "KEY_GENERATED_USING_ABOVE_CODE";
SecretKey mySymmetricKey = new SecretKeySpec(Base64.decodeBase64(myKeyPair.getBytes()), "AES");
EncryptionMaterials materials = new EncryptionMaterials(mySymmetricKey);
AmazonS3Client encryptedS3 = new AmazonS3EncryptionClient(credentials, materials);
try {
File file = new File("D:/dummy.txt");
SSECustomerKey sseKey = new SSECustomerKey(myKeyPair);
PutObjectRequest objectRequest = new PutObjectRequest(bucketName, "withEncrypt/dummy.txt", file);
encryptedS3.putObject(objectRequest.withSSECustomerKey(sseKey));
System.out.println("s3WithEncryption: Object uploaded!!!");
S3Object downloadedObject = encryptedS3.getObject(new GetObjectRequest(bucketName, "withEncrypt/" + file.getName()).withSSECustomerKey(sseKey));
downloadFile("D:/withEncrption", downloadedObject.getObjectContent(), "Steps to configure unifiedUI.txt");
System.out.println("s3WithEncryption: Object Downloaded!!!");
} catch (Exception e) {
e.printStackTrace();
} finally {
encryptedS3.shutdown();
}
}