How to generate secret key using SecureRandom.getInstanceStrong()?
With this code I can receive byte array with random values. Is there any easy way to generate key of a given length (256 bits, for example), type (int, String) and format (hex, bin, dec)?
package com.company;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class KeyGen {
public void generate() throws NoSuchAlgorithmException {
SecureRandom random = SecureRandom.getInstanceStrong();
byte[] values = new byte[32]; // 256 bit
random.nextBytes(values);
StringBuilder sb = new StringBuilder();
for (byte b : values) {
sb.append(String.format("%02x", b));
}
System.out.print("Key: ");
System.out.println(sb.toString());
}
}
Output:
Key: 8fcea84897f48f575c22441ece4e7ddb43ac08cd2c1a83fca46c080768468059
Keys should be of a specific type, e.g. AES. They should preferably be kept inside a SecretKey instance or a similar Key derived class.
Keys for modern symmetric ciphers consist of bits. Usually you would not need a human/String representation of them (and this could actually harm security). Store them in a KeyStore or derive them from a password instead. If you do encode them then the representation format is inconsequential, as long as you don't loose your data during the conversions.
This is probably the best way to generate a strong AES key:
public class GenerateStrongAESKey {
public static SecretKey generateStrongAESKey(final int keysize) {
final KeyGenerator kgen;
try {
kgen = KeyGenerator.getInstance("AES");
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("AES key generator should always be available in a Java runtime", e);
}
final SecureRandom rng;
try {
rng = SecureRandom.getInstanceStrong();
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("No strong secure random available to generate strong AES key", e);
}
// already throws IllegalParameterException for wrong key sizes
kgen.init(keysize, rng);
return kgen.generateKey();
}
public static void main(String[] args) {
SecretKey strongAESKey = generateStrongAESKey(256);
// well, if you must have a human readable string, here it is
// but you've been warned
System.out.println(toHex(strongAESKey.getEncoded()));
}
private static String toHex(final byte[] data) {
final StringBuilder sb = new StringBuilder(data.length * 2);
for (byte b : data) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
}
Note: this requires the unlimited strength jurisdictional files for the Oracle runtime environment for keys > 128 bits.
Related
I implemented a straight simple Java utility class to encrypt and decrypt using AES/GCM/NoPadding. I use this piece of code:
public byte[] encrypt(byte[] input, byte[] key, byte[] iv) throws Exception{
Cipher cipher = initAES256GCMCipher(key, iv, Cipher.ENCRYPT_MODE);
return cipher.doFinal(input);
}
public byte[] decrypt(byte[] input, byte[] key, byte[] iv) throws Exception{
Cipher cipher = initAES256GCMCipher(key, iv, Cipher.DECRYPT_MODE);
return cipher.doFinal(input);
}
private Cipher initAES256GCMCipher(byte[] key, byte[] iv, int encryptionMode) throws Exception{
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(encryptionMode, secretKey, gcmParameterSpec);
return cipher;
}
IV is always a 12-byte array, key is 32-byte array generated with SecureRandom taking a seed. I knowthat on different OS SecureRandom is different, but encryption and decryption are performed on the same OS, so there should be no problem.
Is it quite linear, right? It works perfectly on Windows, ciphering and deciphering return the same text. However, on a Docker image, the same JAR doesn not work: encryption works fine, but decryption throws "AEADBadTagException".
Can you help me, please?
Do not use SecureRandom to derive a key. You'd use a Key Derivation Function (KDF) such as HKDF to do that. But honestly, you have to think of a way to communicate the key - without using SecureRandom.
The problem is that the - relatively secure - SHA1PRNG algorithm is not well defined. The SUN provider does accept a seed that is then used as an only seed iff you seed it before retrieving any random data from it. However, it makes sense that other providers will just mix in the seed to the state of the underlying CSPRNG. This is also the default for most other SecureRandom implementations.
Few SecureRandom implementations fully specify the way that random bits are returned even if the underlying algorithm is specified (with the DRBG). That is usually not a problem if you are just expecting random values. However, if you are using it as a deterministic algorithm such as a KDF (or hash function, for instance) then this becomes a problem and you may get different keys for the same input.
Nowadays you should be able to store AES secret keys within a key store. Not sure if that is a solution for your use case, but it should solve your current issue. Unfortunately Java doesn't contain any official KDF's other than PBKDF1 and PBKDF2 which takes a password rather than a key. Just using a HMAC-SHA256 over some key identifying data using a master key is commonly a good "poor mans KDF".
Here is a quick, initial (but undocumented) implementation that I just created. It mimics the Java JCA without actually going into implementation (as no specific KDF class is defined for it, yet).
You can ask many keys from it with any algorithm specification, as long as the size is below 256 bits (the output of SHA-256) and the label is different for each key that you request. Call getEncoded() on the key if you need data for, for instance, an IV.
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import hex.Hex;
public class PoorMansKDF {
public interface KeyDerivationParameters extends AlgorithmParameterSpec {
String getDerivedKeyAlgorithm();
int getDerivedKeySize();
byte[] getCanonicalInfo();
}
public static class KeyDerivationParametersWithLabel implements KeyDerivationParameters {
private final String algorithm;
private final int keySize;
private final String label;
public KeyDerivationParametersWithLabel(String algorithm, int keySize, String label) {
this.algorithm = algorithm;
this.keySize = keySize;
this.label = label;
}
#Override
public byte[] getCanonicalInfo() {
if (label == null) {
// array without elements
return new byte[0];
}
return label.getBytes(StandardCharsets.UTF_8);
}
#Override
public String getDerivedKeyAlgorithm() {
return algorithm;
}
#Override
public int getDerivedKeySize() {
return keySize;
}
}
private enum State {
CONFIGURED,
INITIALIZED;
}
public static PoorMansKDF getInstance() throws NoSuchAlgorithmException {
return new PoorMansKDF();
}
private final Mac hmac;
private State state;
private PoorMansKDF() throws NoSuchAlgorithmException {
this.hmac = Mac.getInstance("HMACSHA256");
this.state = State.CONFIGURED;
}
public void init(Key key) throws InvalidKeyException {
if (key.getAlgorithm().equalsIgnoreCase("HMAC")) {
key = new SecretKeySpec(key.getEncoded(), "HMAC");
}
hmac.init(key);
this.state = State.INITIALIZED;
}
public Key deriveKey(KeyDerivationParameters info) {
if (state != State.INITIALIZED) {
throw new IllegalStateException("Not initialized");
}
final int keySize = info.getDerivedKeySize();
if (keySize < 0 || keySize % Byte.SIZE != 0 || keySize > hmac.getMacLength() * Byte.SIZE) {
throw new IllegalArgumentException("Required key incompatible with this KDF");
}
final int keySizeBytes = keySize / Byte.SIZE;
// we'll directly encode the info to bytes
byte[] infoData = info.getCanonicalInfo();
final byte[] fullHMAC = hmac.doFinal(infoData);
final byte[] derivedKeyData;
if (fullHMAC.length == keySizeBytes) {
derivedKeyData = fullHMAC;
} else {
derivedKeyData = Arrays.copyOf(fullHMAC, keySizeBytes);
}
SecretKey derivedKey = new SecretKeySpec(derivedKeyData, info.getDerivedKeyAlgorithm());
Arrays.fill(derivedKeyData, (byte) 0x00);
if (fullHMAC != derivedKeyData) {
Arrays.fill(fullHMAC, (byte) 0x00);
}
return derivedKey;
}
// test only
public static void main(String[] args) throws Exception {
var kdf = PoorMansKDF.getInstance();
// input key (zero byte key for testing purposes, use your own 16-32 byte key)
// do not use a password here!
var masterKey = new SecretKeySpec(new byte[32], "HMAC");
kdf.init(masterKey);
// here "enc" is a label, in this case for a derived key for ENCryption
var labeledParameters = new KeyDerivationParametersWithLabel("AES", 256, "enc");
var derivedKey = kdf.deriveKey(labeledParameters);
// use your own hex decoder, e.g. from Apache Commons
System.out.println(Hex.encode(derivedKey.getEncoded()));
var aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
var gcmParams = new GCMParameterSpec(128, new byte[12]);
aesCipher.init(Cipher.ENCRYPT_MODE, derivedKey, gcmParams);
var ct = aesCipher.doFinal();
System.out.println(Hex.encode(ct));
}
}
I have two objects based on which SHA 256 Hash needs to be generated.
First value is a JSONObject
Second value is a String variable.
Ideally, what i need is
Hash hash= new Hash(JSONObject, String);
I couldn't find any hash generation methods which takes two values.
Could anyone help me with this?.
SHA 256 works on a byte array as input. You need to convert your JSONObject and your String to byte arrays, then calculate the SHA 256 hash on the concatenation of these byte arrays.
The proper way of generating a sha256 hashcode using key and value
public static String hashMac(String text, String secretKey)
throws SignatureException {
try {
Key sk = new SecretKeySpec(secretKey.getBytes(), HASH_ALGORITHM);
Mac mac = Mac.getInstance(sk.getAlgorithm());
mac.init(sk);
final byte[] hmac = mac.doFinal(text.getBytes());
return toHexString(hmac);
} catch (NoSuchAlgorithmException e1) {
// throw an exception or pick a different encryption method
throw new SignatureException(
"error building signature, no such algorithm in device "
+ HASH_ALGORITHM);
} catch (InvalidKeyException e) {
throw new SignatureException(
"error building signature, invalid key " + HASH_ALGORITHM);
}
}
public static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
Formatter formatter = new Formatter(sb);
for (byte b : bytes) {
formatter.format("%02x", b);
}
return sb.toString();
}
I have two PublicKey object.I want to compare both for equality or to check which is latest object using java security API or bouncy castle API.How can i achieve this?
You can use equals
if (!key.equals(copyKey)){
System.out.println("not equals!");
}
or check the hashcode of the keys
if (key.hashCode() != copyKey.hashCode())
{
System.out.println("public key hashCode check failed");
}
or compare the hex string of the two public keys
String encodedKey1 = new String(Hex.encode(key1.getEncoded()));
String encodedKey2 = new String(Hex.encode(key2.getEncoded()));
if (!encodedKey1.equals(encodedKey2)){
System.out.println("not equals!");
}
You have a lot of key comparision and check samples at Bouncy Castle Tests, take a look at the org.bouncycastle.jce.provider.test package for some code. BC is not strictly necesary you can do the comparision with the default java security classes.
Normally public keys are compared using some kind of ID. It depends on the protocol how the key ID is calculated. The best method is probably to keep to the PKCS#11 specifications which defines methods of calculating key ID's.
The creation date is not an integral part of the key itself. Either you have to define it elsewhere, or you should use a public key container such as an X509 certificate. Note that you could use the (hex representation of the) key ID to find the creation date in a map.
It's probably best to use a SHA-1 hash over the modulus as ID. The modulus of both the public key and private key are identical and should be different for each key pair. The following code calculates the ID for an RSA public key.
Obviously you can always directly compare the moduli of two keys as well. Key ID's are a bit easier to store though.
public class CreateRSAPublicKeyID {
/**
* Creates a key ID for a given key.
*
* #param key the key
* #return the key ID for the given key
*/
public static byte[] createKeyID(Key key) {
if (key instanceof RSAKey) {
RSAKey rsaKey = (RSAKey) key;
BigInteger modulus = rsaKey.getModulus();
if (modulus.bitLength() % Byte.SIZE != 0) {
throw new IllegalArgumentException("This method currently only works with RSA key sizes that are a multiple of 8 in bits");
}
final byte[] modulusData = i2os(modulus, modulus.bitLength() / Byte.SIZE);
MessageDigest sha1;
try {
sha1 = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("SHA-1 message digest should be available in any Java SE runtime", e);
}
return sha1.digest(modulusData);
}
throw new UnsupportedOperationException("Key type not supported");
}
/**
* Integer to octet string (I2OS) creates a fixed size, left padded, big-endian octet string representation for
* a given integer.
*
* #param i the integer
* #param octets the number of octets (bytes)
* #return the octet string representation of i
*/
public static byte[] i2os(BigInteger i, int octets) {
if (i.bitLength() > octets * Byte.SIZE) {
throw new IllegalArgumentException("i does not fit in " + octets + " octets");
}
final byte[] is = i.toByteArray();
if (is.length == octets) {
return is;
}
final byte[] ius = new byte[octets];
if (is.length == octets + 1) {
System.arraycopy(is, 1, ius, 0, octets);
} else {
System.arraycopy(is, 0, ius, octets - is.length, is.length);
}
return ius;
}
public static String toHex(byte[] data) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length; i++) {
sb.append(String.format("%02X", data[i]));
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair pair = kpg.generateKeyPair();
byte[] keyID = createKeyID(pair.getPublic());
System.out.println(toHex(keyID));
}
}
Note that the getModulus() command may not be compatible with some keystores (e.g. those ones that represent HSM tokens or smart cards).
Lookin at Oracle's documentation, I think you can compare PublicKey using its 3 getters : getAlgorithm, getEncoded, getFormat doing this :
oldKey.getAlgorithm().equals(newKey.getAlgorithm()) and so on.
I encountered the abovementioned exception while I was decrypting a string.
Below is my code:
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
public class EncryptAndDecrypt {
public static Cipher createCipher () throws Exception{
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
return cipher;
}
public static KeyPair generateKey () throws NoSuchAlgorithmException{
KeyPairGenerator keyGen = KeyPairGenerator.getInstance ("RSA");
keyGen.initialize(1024);
KeyPair key = keyGen.generateKeyPair();
return key;
}
public static byte [] encrypt (String str, Cipher cip, KeyPair key) {
byte [] cipherText = null;
try {
byte [] plainText = str.getBytes("UTF8");
cip.init(Cipher.ENCRYPT_MODE, key.getPublic());
cipherText = cip.doFinal(plainText);
} catch (Exception e) {
e.printStackTrace();
}
return cipherText;
}
public static String decrypt (byte [] c, Cipher cip, KeyPair key) throws Exception {
cip.init(Cipher.DECRYPT_MODE, key.getPrivate());
byte [] decryptedPlainText = cip.doFinal (c);// exception occurred here
String decryptedPlainStr = new String (decryptedPlainText);
return decryptedPlainStr;
}
}
//separate class below to use the encrypt method
public class EncryptionApp {
public static void main (String [] args) {
getEncrypted();
}
public static byte [] getEncrypted () {
byte [] encyptedByte = null;
try {
String plainText = "der";
Cipher cip = Safety.createCipher();
KeyPair key = Safety.generateKey();
encyptedByte = Safety.useRSA(plainText, cip, key);
}
catch (Exception e) {
e.printStackTrace();
}
return encyptedByte;
}
}
// Another class to use the decrypt method
public class DecryptionApp {
public static void main(String[] args) {
System.out.println (useDecrypted () );
}
public static byte[] useDecrypted () {
byte [] decryptedText = null;
try {
Cipher cip = EncryptAndDecrypt.createCipher();
KeyPair key = EncryptAndDecrypt.generateKey();
decryptedText = EncryptAndDecrypt.decrypt(EncryptionApp.getEncrypted(),cip,key);
}
catch (Exception e) {
e.printStackTrace();
}
return decryptedText;
}
}
You already asked the same question in "javax.crypto.BadPaddingException: Data must start with zero" exception, and I gave you an answer: you're using two different keypairs : one to encrypt, and another one to decrypt. That can't work. I even gave you a code sample showing that everything ran fine if you used the same keypair.
KeyPairGenerator.generateKeyPair() generates a keypair. Calling this method twice will get you two different keypairs: it uses a random number generator internally to generate always different keypairs.
You must generate a keypair once, store it in a variable, and use this variable to encrypt and decrypt.
You should read the documentation of the classes and methods you are using. The documentation of generateKeyPair says:
This will generate a new key pair
every time it is called.
Add this main method to EncryptAndDecrypt, and execute it. You'll see that evrything works fine.
public static void main(String[] args) throws Exception {
String s = "hello";
Cipher cipher = createCipher();
KeyPair keyPair = generateKey();
byte[] b = encrypt(s, cipher, keyPair);
String s2 = decrypt(b, cipher, keyPair);
System.out.println(s2);
}
The problem lies in the way you're using this class.
The useDecrypted method does the following:
Cipher cip = EncryptAndDecrypt.createCipher(); // create a Cipher object using EncryptAndDecrypt
KeyPair key = EncryptAndDecrypt.generateKey(); // generate a KeyPair using EncryptAndDecrypt
// call EncryptionApp.getEncrypted() to get an encrypted text, then decrypt this encrypted text
// using the keypair created above.
decryptedVote = EncryptAndDecrypt.decrypt(EncryptionApp.getEncrypted(), cip, key);
And the getEncrypted method does the following:
String plainText = "der"; // create some plain text
// create a Cipher instance. Is it the same algorithm as the one in useDecrypted?
// we don't know, because it uses another, unknown, Safety class
Cipher cip = Safety.createCipher();
// create a new KeyPair instance. Is it the same KeyPair as the one in useDecrypted?
// No : another keypair is generated. There is no way something encrypted using a keypair
// will decrypt correctly with another keypair.
KeyPair key = Safety.generateKey();
encyptedByte = Safety.useRSA(plainText, cip, key);
So, in short, you use two different keypairs : one to encrypt and the other to decrypt. That can't work.
Also, note that in encrypt, you transform your string into a byte array using the UTF8 encoding, whereas in decrypt, you transform the byte array into a String using the default platform encoding. You should use UTF8 for both, and thus use the following code in decrypt :
String decryptedPlainStr = new String (decryptedPlainText, "UTF8");
Have you googled? A lot of people have this problem when the key to encrypt is not the same as the key to decrypt. It seems like you generate new keys all the time instead of using the same key to decrypt that you used for encryption.
I was getting this error and it turned out in my case to be that the base 64 string I was sending as a parameter contained some characters that were being altered because of being in a URL. The solution turned out to be URL encoding the parameter.
I encountered the abovementioned exception while I was decrypting a string.
Below is my code:
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
public class EncryptAndDecrypt {
public static Cipher createCipher () throws Exception{
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
return cipher;
}
public static KeyPair generateKey () throws NoSuchAlgorithmException{
KeyPairGenerator keyGen = KeyPairGenerator.getInstance ("RSA");
keyGen.initialize(1024);
KeyPair key = keyGen.generateKeyPair();
return key;
}
public static byte [] encrypt (String str, Cipher cip, KeyPair key) {
byte [] cipherText = null;
try {
byte [] plainText = str.getBytes("UTF8");
cip.init(Cipher.ENCRYPT_MODE, key.getPublic());
cipherText = cip.doFinal(plainText);
} catch (Exception e) {
e.printStackTrace();
}
return cipherText;
}
public static String decrypt (byte [] c, Cipher cip, KeyPair key) throws Exception {
cip.init(Cipher.DECRYPT_MODE, key.getPrivate());
byte [] decryptedPlainText = cip.doFinal (c);// exception occurred here
String decryptedPlainStr = new String (decryptedPlainText);
return decryptedPlainStr;
}
}
//separate class below to use the encrypt method
public class EncryptionApp {
public static void main (String [] args) {
getEncrypted();
}
public static byte [] getEncrypted () {
byte [] encyptedByte = null;
try {
String plainText = "der";
Cipher cip = Safety.createCipher();
KeyPair key = Safety.generateKey();
encyptedByte = Safety.useRSA(plainText, cip, key);
}
catch (Exception e) {
e.printStackTrace();
}
return encyptedByte;
}
}
// Another class to use the decrypt method
public class DecryptionApp {
public static void main(String[] args) {
System.out.println (useDecrypted () );
}
public static byte[] useDecrypted () {
byte [] decryptedText = null;
try {
Cipher cip = EncryptAndDecrypt.createCipher();
KeyPair key = EncryptAndDecrypt.generateKey();
decryptedText = EncryptAndDecrypt.decrypt(EncryptionApp.getEncrypted(),cip,key);
}
catch (Exception e) {
e.printStackTrace();
}
return decryptedText;
}
}
You already asked the same question in "javax.crypto.BadPaddingException: Data must start with zero" exception, and I gave you an answer: you're using two different keypairs : one to encrypt, and another one to decrypt. That can't work. I even gave you a code sample showing that everything ran fine if you used the same keypair.
KeyPairGenerator.generateKeyPair() generates a keypair. Calling this method twice will get you two different keypairs: it uses a random number generator internally to generate always different keypairs.
You must generate a keypair once, store it in a variable, and use this variable to encrypt and decrypt.
You should read the documentation of the classes and methods you are using. The documentation of generateKeyPair says:
This will generate a new key pair
every time it is called.
Add this main method to EncryptAndDecrypt, and execute it. You'll see that evrything works fine.
public static void main(String[] args) throws Exception {
String s = "hello";
Cipher cipher = createCipher();
KeyPair keyPair = generateKey();
byte[] b = encrypt(s, cipher, keyPair);
String s2 = decrypt(b, cipher, keyPair);
System.out.println(s2);
}
The problem lies in the way you're using this class.
The useDecrypted method does the following:
Cipher cip = EncryptAndDecrypt.createCipher(); // create a Cipher object using EncryptAndDecrypt
KeyPair key = EncryptAndDecrypt.generateKey(); // generate a KeyPair using EncryptAndDecrypt
// call EncryptionApp.getEncrypted() to get an encrypted text, then decrypt this encrypted text
// using the keypair created above.
decryptedVote = EncryptAndDecrypt.decrypt(EncryptionApp.getEncrypted(), cip, key);
And the getEncrypted method does the following:
String plainText = "der"; // create some plain text
// create a Cipher instance. Is it the same algorithm as the one in useDecrypted?
// we don't know, because it uses another, unknown, Safety class
Cipher cip = Safety.createCipher();
// create a new KeyPair instance. Is it the same KeyPair as the one in useDecrypted?
// No : another keypair is generated. There is no way something encrypted using a keypair
// will decrypt correctly with another keypair.
KeyPair key = Safety.generateKey();
encyptedByte = Safety.useRSA(plainText, cip, key);
So, in short, you use two different keypairs : one to encrypt and the other to decrypt. That can't work.
Also, note that in encrypt, you transform your string into a byte array using the UTF8 encoding, whereas in decrypt, you transform the byte array into a String using the default platform encoding. You should use UTF8 for both, and thus use the following code in decrypt :
String decryptedPlainStr = new String (decryptedPlainText, "UTF8");
Have you googled? A lot of people have this problem when the key to encrypt is not the same as the key to decrypt. It seems like you generate new keys all the time instead of using the same key to decrypt that you used for encryption.
I was getting this error and it turned out in my case to be that the base 64 string I was sending as a parameter contained some characters that were being altered because of being in a URL. The solution turned out to be URL encoding the parameter.