How to use Key Interface for secret key in java? - java

I have a code from creating base 64 hashes
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class ApiSecurityExample {
public static void main(String[] args) {
try {
String secret = "secret";
String message = "Message";
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
String hash = Base64.encodeBase64String(sha256_HMAC.doFinal(message.getBytes()));
System.out.println(hash);
}
catch (Exception e){
System.out.println("Error");
}
}
}
there is secret_key in sha256_HMAC.init(secret_key);
when I read, it tells use Key key an Interface.
how to use it?

The example is doing it wrong, as strings should not be used to store keys.
A secret key should consist of bytes that are unpredictable to an adversary. The most logical method to generate those is to use a random number generator, but you can also generate them from key establishment (Diffie-Hellman), using a key derivation function upon another key, ratchets and many other ways.
A somewhat dangerous method is to generate them from a password. For that you normally use a Password Based Key Derivation Function or PBKDF. Java has direct support for PBKDF2 which can be used for this.
So you could create a HMAC key in the following way:
Mac mac = Mac.getInstance("HMACSHA256");
SecureRandom rng = new SecureRandom();
// key size can be anything but should default to the hash / MAC output size for HMAC
byte[] hmacKeyData = new byte[mac.getMacLength()];
rng.nextBytes(hmacKeyData);
SecretKey hmacKey = new SecretKeySpec(hmacKeyData, "HMACSHA256");
Arrays.fill(hmacKeyData, (byte) 0x00);
However, the following code is shorter, probably more descriptive. It also allows hardware devices to be used later on to implement the Mac, although that might be a bit out of your territory.
KeyGenerator kg = KeyGenerator.getInstance("HMACSHA256");
SecretKey hmacKey = kg.generateKey();
Finally, if you still want to use a password, then use PKBDF2 and don't forget to store the salt:
// you don't want to use a string, as you cannot delete strings in Java
char[] password = {'p', 'a', 's', 's' };
SecureRandom rng = new SecureRandom();
byte[] salt = new byte[128 / Byte.SIZE];
rng.nextBytes(salt);
int iterations = 1_000_000;
Mac mac = Mac.getInstance("HMACSHA256");
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, mac.getMacLength() * Byte.SIZE);
SecretKeyFactory pbkdf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hmacKeyData = pbkdf.generateSecret(spec).getEncoded();
SecretKey hmacKey = new SecretKeySpec(hmacKeyData, "HMACSHA256");
// clean up secret material
Arrays.fill(password, (char) 0x0000);
spec.clearPassword();
Arrays.fill(hmacKeyData, (byte) 0x00);
As an attacker may have forever to try passwords if he has a MAC to compare the result with, it would be a very good idea to choose a very complex password though; this is why password based encryption generally is not a good idea.
Key is a generic parent interface used for both SecretKey, PublicKey and PrivateKey. It is used in many classes that represent crypto algorithms as they may be used with any kind of key. For instance Cipher can be used for RSA but also for AES. So the implementation just checks at runtime if the correct key is given.
For Mac it might as well have been SecretKey as a Mac is really always a symmetrical algorithm (an asymmetric form of a Mac is called a Signature after all). Just a HMAC key would not be enough though, as there are also Mac algorithms based on block ciphers such as AES (thus requiring a SecretKey with algorithm "AES").
For convenience, SecretKeySpec also implements SecretKey; that way you don't need the SecretKeyFactory to create a SecretKey. The Java designers kind of forgot about hardware support that does require such as factory, but here we are.

Related

Java AES encryption issue

Every time the encryption values changed by using AES, let anyone investigate the below code and let me know the issue
code:
private static final String secretKeys = "58BA833E57A51CBF9BF8BAB696BF9"
public static String encrypt() throws Exception {
byte[] salt = new byte[16];
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
PBEKeySpec pbeKeySpec = new PBEKeySpec(secretKeys.getChars(),salt,1000, 256);
Key secretKey = factory.generateSecret(pbeKeySpec);
byte[] key = new byte[32];
byte[] iv = new byte[16];
SecretKeySpec secret = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] result = cipher.doFinal("welcome".getBytes("UTF-8"));
String s = Base64.getEncoder().encodeToString(result);
return s
}
Output
first time I got the below-encrypted string
CZRIP35M4CnJtuDQ6YpmaQ==
The second time I got the below-encrypted string
/fylTjohAZDsnCaHhiZo3A==
I have three questions:
why the encrypted string not a constant?
how can I set the Blocksize? ( AES.BlockSize = 128;)
How can I set the padding mode? (AES.Padding = PaddingMode.PKCS7;)
For the first question, #Freiheit already answered this.
Long story short, based on the iv (initilization vector) which acts as a salt and will be different for each encryption.
Having that said, encrypting the same plain text will result in different encrypted text, but the decryption (if necessary) will result back into the same plain text.
IV is helpful to make the encryption predictable.
Having stored the same password for 2 different users in a database will have different values, but will be the same password.
With the current cipher configured, you already have 128 block size. You can read more about the different cypher transformation here. You can also find more information of the block sizes for different algorithms here
You just need to change the Cipher.getInstance() to AES/CBC/PKCS7Padding
1) the encrypted text is always different because the Cipher initialization is providing it's own IV since you are not providing one. You need to provide the IV you've "computed" in order to have a consistent output. Remember you never want to use an IV more than once for whatever this code is ultimately intended to do.
2) The keysize can be 128, 192 or 256 but the blocksize is always 128.
3) Java only provides PKCS5, but there is no difference in the implementation for AES. see what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding
As was already pointed out there are several problems with the code provided such as the first lines not actually doing anything and the key and iv both being uninitialized. I would additionally suggest you use SecureRandom to initialize your key and iv. If you plan on using only a single AES key, this can be computed once and placed in the code or configuration file instead of running PBKDF2 every time.
Only adding to the answer provided by #micker, you need to invoke another version of Cipher.init(); one that takes the IV into account:
...
byte[] iv = new byte[16];
IvParameterSpec ivSpec = new IvParameterSpec(iv); // <= Wrap your IV bytes here.
SecretKeySpec secret = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivSpec); // <= Add IV here.
...
That being said, the implementation suffers from a slew of other issues (key being all zeroes, IV being all zeroes, first 4 line don't do anything for you (as #JBNizet pointed out)). I hope you are only using it to study how Java's encryption mechanics works.

How to decrypt password from encrypted password

Currently we are encrypting the passwords entered by the user from login page and store them in the Database. Here am developing a new login page for internal purpose and reusing the same username and encrypted password. If user is authorised, then will allow him to access the reports. Here my question is, how can I get the secret key which they have used to encrypt. Would like to use the same key to decrypt the password and I can go ahead with my logic.
This is the code we are using to encrypt method to encrypt the password.
user = userRemote.loginUser(userName, new String(EncryptDecrypt.storePassword(password),"Cp1252"));
Here password is Password entered in the login page.
This is the method to encrypt the password.
final static byte[] salt = {
(byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c,
(byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99
};
final static int count = 1;
public static byte[] storePassword(char[] password) throws InternalException {
PBEKeySpec pbeKeySpec;
PBEParameterSpec pbeParamSpec;
SecretKeyFactory keyFac;
byte[] ciphertext = null;
try {
// Install SunJCE provider
Provider sunJce = new com.sun.crypto.provider.SunJCE();
Security.addProvider(sunJce);
// Create PBE parameter set
pbeParamSpec = new PBEParameterSpec(salt, count);
pbeKeySpec = new PBEKeySpec(password);
keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
// Create PBE Cipher
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
// Our cleartext
byte[] cleartext = (new String(password)).getBytes("Cp1252");
// Encrypt the cleartext
ciphertext = pbeCipher.doFinal(cleartext);
} catch (BadPaddingException ex) {
log.error("EncryptDecrypt: " + ex.getMessage());
throw new InternalException(ex.getMessage());
} catch (Exception ex) {
log.error("EncryptDecrypt: " + ex.getMessage());
throw new InternalException(ex.getMessage());
}
return ciphertext;
}
This is the class am using to decrypt the password. Here I have only encrypted password as an input to decrypt the password. For example •Ä0BÒ¦O , so am using the same to generate secret key and decrypt it. But, getting below exception.
java.security.spec.InvalidKeySpecException: Password is not ASCII
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class DecryptPassword {
public static void main(String[] args) {
String decryptedStr = checkPassword("•Ä0BÒ¦O");
System.out.println("decryptedStr : "+decryptedStr);
}
final static byte[] salt = {
(byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c,
(byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99
};
final static int count = 1;
static String decryptedPassword = "";
public static String checkPassword(String encryptedPassword) {
PBEKeySpec pbeKeySpec;
PBEParameterSpec pbeParamSpec;
SecretKeyFactory keyFac;
try {
// Install SunJCE provider
Provider sunJce = new com.sun.crypto.provider.SunJCE();
Security.addProvider(sunJce);
// Create PBE parameter set
pbeParamSpec = new PBEParameterSpec(salt, count);
pbeKeySpec = new PBEKeySpec(encryptedPassword.toCharArray());
keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
// Create PBE Cipher
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);
byte[] decrypted = pbeCipher.doFinal(encryptedPassword.getBytes());
decryptedPassword = decrypted.toString();
} catch (BadPaddingException ex) {
System.out.println("EncryptDecrypt: " + ex.getMessage());
} catch (Exception ex) {
System.out.println("EncryptDecrypt: " + ex.getMessage());
}
return decryptedPassword;
}
}
Here I should be able to decrypt the password successfully, but not.Can anyone please help me what am missing here? Thanks In Advance.
That's no encryption, that's a hash! (rim-shot)
That storePassword routine effectively uses the password to encrypt itself; more exactly, PBEwithMD5andDES uses a key derived from the password by the original PKCS5v1 derivation function, now retronymed PBKDF1 for clarity, instantiated with MD5, fixed salt, and 1 (!) iteration, to encrypt with original DES CBC the password. This is a variation on a formerly popular way of creating a cryptographic hash.
During the 1960s and 1970s and maybe 1980s, before cryptologists turned their attention to designing specific cryptographic hash functions so ciphers were the only crypto primitive, a common method of turning a cipher (a keyed permutation) into a crypto hash (an unkeyed function) was to use the data as key to encrypt a constant; a slight variation of this was implemented as a library function inaccurately named crypt and used as the password hashing function in early Unix ca 1970, and is still remembered (sometimes even used) but now often retronymed DES-crypt or descrypt to distinguish from the alternatives and replacements developed since.
Unlike a cipher which is designed to be decrypted, this is a hash and designed NOT to be reversed. I don't know of any way to reverse this method easier than brute force (i.e. an analytic 'break'), but original DES, now usually called single-DES (or 1DES) to distinguish from its direct successor triple-DES (or 3DES or formally TDEA), is weak enough it can now be brute forced if you really want to. For example JTR reports roughly 10M-30M trials/sec for single-salt descrypt, which would correspond to roughly 0.5G/sec for simple DES, so trying all DES keys would take several years with one computer, a few days with a thousand computers, or a few minutes with a million computers. Data on hashcat is harder to find but appears roughly comparable. If you have any clue how the password was chosen, it may be faster to try only the possible passwords rather than all possible keys.
But don't. The correct way to verify a password hash is for the user to supply the claimed password, repeat the hashing process with the same parameters (here easy because it doesn't use a variable salt as it should), and see whether the new hashed result matches the stored one.

Reusing PBKDF2 salt for AES/GCM as IV: dangerous?

I'm developing an encryption utility class to be reused for common operations.
A very common case is to encrypt a plaintext with a user-provided password.
In this case, I'm using PBKDF2 to derive a valid AES key, then use it in GCM mode to encrypt the plaintext.
Some code:
// IV_LEN = 96
// ITERATIONS = 1000 ~ 4000
// KEY_LEN = 128 ~ 256
// TAG_LEN = 128
public static String encrypt(byte[] plain, char[] password) throws GeneralSecurityException
{
SecureRandom rng = SecureRandom.getInstanceStrong();
byte[] iv = new byte[IV_LEN / 8];
rng.nextBytes(iv);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
SecretKey derivedKey = factory.generateSecret(new PBEKeySpec(password, iv, ITERATIONS, KEY_LEN));
SecretKey secretKey = new SecretKeySpec(derivedKey.getEncoded(), "AES");
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
c.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LEN, iv));
byte[] encrypted = c.doFinal(plain);
Encoder encoder = Base64.getUrlEncoder().withoutPadding();
return encoder.encodeToString(iv) + ":" + encoder.encodeToString(encrypted);
}
Currently, I'm using the PBKDF2 salt (96 bit - SecureRandom) also as the IV for AES/GCM encryption.
Both the salt and IV can be public, but they shouldn't be reused.
Is it to be understood that they shouldn't be reused within the same Feature/Service/Algorithm, or that they shouldn't be reused anywhere?
It's very easy to modify this method to generate different salt and IV, but is there a reason to do it?
Thanks
Note that you may not need to re-generate a random IV as long as you change the salt and therefore the resulting key. If you do not change the salt each time you (re-)encrypt then you do need a separate IV or you may leak information to an adversary.
You can keep the salt and IV the same. But it is generally easier to derive the IV together with the key from the password and salt. If you use PBKDF2 with a SHA-512 hash this is easy: just take 128, 192 or 256 bits of AES key from the resulting hash and then take another 128 subsequent bits as IV and use that.
If you need multiple keys or if you use a smaller hash then you may need to derive more keys from the result of PBKDF2. In that case it is best to mark the result of PBKDF2 as a master secret and perform N key derivations from it, one for each key and one for the IV. You could use e.g. HKDF for this, which Bouncy Castle has an implementation of (for which I provided the initial source code).

String to byte[] with a defined size

What i am trying to do is convert a String into a byte[] but i need the byte[] size to be 64 bytes always, independently of what string is on the input in order to be used with Realm encryption. However, after some research, I couldn't find anything similar, or is there any other way I can use a string as the Realm encryption?
This is my code:
String passphrase = "ASDYB982234235512";
byte[] key = passphrase.getBytes();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder()
.encryptionKey(key)
.build();
This is the error:
java.lang.IllegalArgumentException: The provided key must be 64 bytes. Yours was: 17
You want to use a Key Derivation Function to generate an encryption key from the user's passphrase. The simplest form of this is to add a salt to the user's passphrase and then feed it into SHA-2 to get a 256-bit hash, but ideally you want to use something like scrypt to make it harder to brute-force the passphrase.
Here https://android-developers.googleblog.com/2013/02/using-cryptography-to-store-credentials.html you can find the function you can use (I just changed the key size):
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 = 1000;
// Generate a 512-bit key
final int outputKeyLength = 512;
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey;
}
And then use secretKey.getEncoded().
To generate salt:
final Random secureRandom = new SecureRandom();
byte[] salt = new byte[32];
secureRandom.nextBytes(salt);
Remember to save the salt and keep it somewhere (in Preferences for instance).
Try encoding the string using Base64.encode, i guess the output will be useful to you.
Or try using a string with 64 characters in it your current passphrase contains 17 characters

Translating C# RSACryptoServiceProvider code to Java

I need to encrypt String for project related purpose and was given the below code for the same by vendor.
public static string EncryptString(string StringToEncrypt)
{
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
string xmlString = "<RSAKeyValue><Modulus>qqoWhMwGrrEBRr92VYud3j+iIEm7652Fs20HvNckH3tRDJIL465TLy7Cil8VYxJre69zwny1aUAPYItybg5pSbSORmP+hMp6Jhs+mg3qRPvHfNIl23zynb4kAi4Mx/yEkGwsa6L946lZKY8f9UjDkLJY7yXevMML1LT+h/a0a38=</Modulus><Exponent>AQAB</Exponent><P>20PwC7nSsfrfA9pzwSOnRYdbhOYivFSuERxvXHvNjCll5XdmFYYp1d2evXcXbyj3E1k8azce1avQ9njH85NMNQ==</P><Q>x0G0lWcQ13NDhEcWbA7R2W5LPUmRqcjQXo8qFIaHk7LZ7ps9fAk/kOxaCR6hvfczgut1xSpXv6rnQ5IGvxaHYw==</Q><DP>lyybF2qSEvYVxvFZt8MeM/jkJ5gIQPLdZJzHRutwx39PastMjfCHbZW0OYsflBuZZjSzTHSfhNBGbXjO22gmNQ==</DP><DQ>NJVLYa4MTL83Tx4vdZ7HlFi99FOI5ESBcKLZWQdTmg+14XkIVcZfBxDIheWWi3pEFsWqk7ij5Ynlc/iCXUVFvw==</DQ><InverseQ>X5Aw9YSQLSfTSXEykTt7QZe6SUA0QwGph3mUae6A2SaSTmIZTcmSUsJwhL7PLNZKbMKSWXfWoemj0EVUpZbZ3Q==</InverseQ><D>jQL4lEUYCGNMUK6GEezIRgiB5vfFg8ql3DjsOcXxnOmBcEeD913kcYnLSBWEUFW55Xp0xW/RXOOHURgnNnRF3Ty5UR73jPN3/8QgMSxV8OXFo3+QvX+KHNHzf2cjKQDVObJTKxHsHKy+L2qjfULA4e+1cSDNn5zIln2ov51Ou3E=</D></RSAKeyValue>";
provider.FromXmlString(xmlString);
return Convert.ToBase64String(provider.Encrypt(Encoding.ASCII.GetBytes(StringToEncrypt), false));
}
However I need to modify or translate it to JAVA. I have wrote the below method for the same purpose.
public static String EncryptString(String strToBeEncrypted) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException
{
String modulusString = "qqoWhMwGrrEBRr92VYud3j+iIEm7652Fs20HvNckH3tRDJIL465TLy7Cil8VYxJre69zwny1aUAPYItybg5pSbSORmP+hMp6Jhs+mg3qRPvHfNIl23zynb4kAi4Mx/yEkGwsa6L946lZKY8f9UjDkLJY7yXevMML1LT+h/a0a38=";
String publicExponentString = "AQAB";
byte[] modulusBytes = Base64.decodeBase64(modulusString);
byte[] exponentBytes = Base64.decodeBase64(publicExponentString);
BigInteger modulus = new BigInteger(1, modulusBytes);
BigInteger publicExponent = new BigInteger(1, exponentBytes);
RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, publicExponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(rsaPubKey);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] plainBytes = strToBeEncrypted.getBytes("US-ASCII");
byte[] cipherData = cipher.doFinal(plainBytes);
String encryptedStringBase64 = Base64.encodeBase64String(cipherData);
return encryptedStringBase64;
}
But the sample results do not match.
String is "4111111111111111" and encrypted result should be:
PfU31ai9dSwWX4Im19TlikfO9JetkJbUE+btuvpBuNHTnnfrt4XdM4PmGA19z8rF+lPUC/kcOEXciUSxFrAPyuRJHifIDqWFbbJvPhatbf269BXUiAW31UBX3X5bBOqNWjh4LDitYY0BtarlTU4xzOFyb7vLpLJe9aHGWhzs6q0=
But the result from Java code is
Cxp5AIzTHEkrU6YWwYo5yYvpED2qg9IC/0ct+tRgDZi9fJb8LAk+E1l9ljEt7MFQ2KB/exo4NYwijnBKYPeLStXyfVO1Bj6S76zMeKygAlCtDukq1UhJaJKaCXY94wi9Kel09VTmj+VByIYvAGUFqZGaK1CyLnd8QXMcdcWi3sA=
Every encryption algorithm needs to be randomized in order to provide semantic security. Otherwise, an attacker might notice that you've sent the same message again, just by observing ciphertexts. In symmetric ciphers, this property is achieved by a random IV. In RSA, this is achieved by a randomized padding (PKCS#1 v1.5 type 2 and PKCS#1 v2.x OAEP are randomized).
You can check whether the padding is randomized by running the encryption again with the same key and plaintext, and comparing the ciphertexts to previous ciphertexts. If the ciphertexts change in either C# or Java between executions, then you will not be able to tell whether the encryption is compatible, just by looking at the ciphertexts.
The proper way to check this, would be to encrypt something in one language and then decrypt in the other. For full compatibility, you should also try it the other way around.
Looking at your code, both seem equivalent, because false is passed as the second parameter into RSACryptoServiceProvider#Encrypt to use PKCS#1 v1.5 padding, and Cipher.getInstance("RSA/ECB/PKCS1PADDING") requests the same padding. The input/output encodings also seem equivalent. So, yes this code will be equivalent.
PKCS#1 v1.5 padding should not be used nowadays, because it is vulnerable against a Bleichenbacher attack (reference). You should use OAEP for encryption and PSS for signing, which are considered secure. C# and Java both support OAEP, but there may be differences in the default hash functions that are used (hash and MGF1).

Categories