I'm trying to figure out how to practically use argon2 hashing for passwords in Java. I've got to be missing something, because none of the APIs return discrete fields for the hash or the salt. I've tried both a JVM binding for argon2 and also spring-security + bouncy castle and both give me a String, but it's also serialized with information beyond just the hashed password and salt.
public static void main(String[] args) {
final String rawPass = "badPassword";
// argon2-jvm
Argon2 argon2jvm = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id, 16, 32);
String arg2JvmHash = argon2jvm.hash(10, 65536, 1, rawPass.getBytes(StandardCharsets.UTF_8));
System.out.println("argon2-jvm:");
System.out.println(arg2JvmHash);
System.out.println("\n\n");
// spring security + bouncy castle
Argon2PasswordEncoder arg2SpringSecurity = new Argon2PasswordEncoder(16, 32, 1, 65536, 10);
String springBouncyHash = arg2SpringSecurity.encode(rawPass);
System.out.println("spring security + bouncy castle:");
System.out.println(springBouncyHash);
System.out.println("\n\n");
}
And then here are the results:
argon2-jvm:
$argon2id$v=19$m=65536,t=10,p=1$BeHo0SdgM6vt5risz+yuLg$dOBFlfeoPPGCk/OLCGJ9sRhyPl0zMqMAUZvkltFWxnA
spring security + bouncy castle:
$argon2id$v=19$m=65536,t=10,p=1$i9iHBeHankerOJhfUvXrnQ$8Ldr1QkPglW0DSjYqoaoAy0brxs1vPVhlm4174NdR80
How do I get the discrete values of the hashes and salts? In my research, it sounds like I can parse out this output by myself, but that sounds like a bad idea.
Am I using the wrong libraries? I've been doing a lot of research and these are the two most popular libraries that keep showing up.
I'm using Bouncy Castle to implement Argon2id as it allows to set the parameters and salt instead of parsing the output.
The below full running program uses 4 parameter sets - the parameter were taken from PHP's OpenSSL implementation but you can choose the parameter individually of course.
As the program is taken from a Cross platform project it uses a fixed salt that is UNSECURE - in production you need to use a randomly generated salt.
This is an output:
Generate a 32 byte long encryption key with Argon2id
password: secret password
salt (Base64): AAAAAAAAAAAAAAAAAAAAAA==
encryptionKeyArgon2id (Base64) minimal: e9G7+HHmftUaCEP2O1NwCSJkfyAT0QBzod3Szm1elf0=
encryptionKeyArgon2id (Base64) interactive: FZcsUwo7wf7V24qWTwKeSN9//+Pxy2gCKN35KZX2hXs=
encryptionKeyArgon2id (Base64) moderate: gdizE6kia1W/CgTA3bRKKjtaf8cgZL1BIe6jeDegg0c=
encryptionKeyArgon2id (Base64) sensitive: 19Uym9wI6e/l5f0NocZmNEaouoHvsSyVfrp9iRYl/C8=
code:
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class Argon2id {
public static void main(String[] args) {
// uses Bouncy Castle
System.out.println("Generate a 32 byte long encryption key with Argon2id");
String password = "secret password";
System.out.println("password: " + password);
// ### security warning - never use a fixed salt in production, this is for compare reasons only
byte[] salt = generateFixedSalt16Byte();
// please use below generateSalt16Byte()
//byte[] salt = generateSalt16Byte();
System.out.println("salt (Base64): " + base64Encoding(salt));
// ### the minimal parameter set is probably UNSECURE ###
String encryptionKeyArgon2id = base64Encoding(generateArgon2idMinimal(password, salt));
System.out.println("encryptionKeyArgon2id (Base64) minimal: " + encryptionKeyArgon2id);
encryptionKeyArgon2id = base64Encoding(generateArgon2idInteractive(password, salt));
System.out.println("encryptionKeyArgon2id (Base64) interactive: " + encryptionKeyArgon2id);
encryptionKeyArgon2id = base64Encoding(generateArgon2idModerate(password, salt));
System.out.println("encryptionKeyArgon2id (Base64) moderate: " + encryptionKeyArgon2id);
encryptionKeyArgon2id = base64Encoding(generateArgon2idSensitive(password, salt));
System.out.println("encryptionKeyArgon2id (Base64) sensitive: " + encryptionKeyArgon2id);
}
// ### the minimal parameter set is probably UNSECURE ###
public static byte[] generateArgon2idMinimal(String password, byte[] salt) {
int opsLimit = 2;
int memLimit = 8192;
int outputLength = 32;
int parallelism = 1;
Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
.withVersion(Argon2Parameters.ARGON2_VERSION_13) // 19
.withIterations(opsLimit)
.withMemoryAsKB(memLimit)
.withParallelism(parallelism)
.withSalt(salt);
Argon2BytesGenerator gen = new Argon2BytesGenerator();
gen.init(builder.build());
byte[] result = new byte[outputLength];
gen.generateBytes(password.getBytes(StandardCharsets.UTF_8), result, 0, result.length);
return result;
}
public static byte[] generateArgon2idInteractive(String password, byte[] salt) {
int opsLimit = 2;
int memLimit = 66536;
int outputLength = 32;
int parallelism = 1;
Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
.withVersion(Argon2Parameters.ARGON2_VERSION_13) // 19
.withIterations(opsLimit)
.withMemoryAsKB(memLimit)
.withParallelism(parallelism)
.withSalt(salt);
Argon2BytesGenerator gen = new Argon2BytesGenerator();
gen.init(builder.build());
byte[] result = new byte[outputLength];
gen.generateBytes(password.getBytes(StandardCharsets.UTF_8), result, 0, result.length);
return result;
}
public static byte[] generateArgon2idModerate(String password, byte[] salt) {
int opsLimit = 3;
int memLimit = 262144;
int outputLength = 32;
int parallelism = 1;
Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
.withVersion(Argon2Parameters.ARGON2_VERSION_13) // 19
.withIterations(opsLimit)
.withMemoryAsKB(memLimit)
.withParallelism(parallelism)
.withSalt(salt);
Argon2BytesGenerator gen = new Argon2BytesGenerator();
gen.init(builder.build());
byte[] result = new byte[outputLength];
gen.generateBytes(password.getBytes(StandardCharsets.UTF_8), result, 0, result.length);
return result;
}
public static byte[] generateArgon2idSensitive(String password, byte[] salt) {
int opsLimit = 4;
int memLimit = 1048576;
int outputLength = 32;
int parallelism = 1;
Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
.withVersion(Argon2Parameters.ARGON2_VERSION_13) // 19
.withIterations(opsLimit)
.withMemoryAsKB(memLimit)
.withParallelism(parallelism)
.withSalt(salt);
Argon2BytesGenerator gen = new Argon2BytesGenerator();
gen.init(builder.build());
byte[] result = new byte[outputLength];
gen.generateBytes(password.getBytes(StandardCharsets.UTF_8), result, 0, result.length);
return result;
}
private static byte[] generateSalt16Byte() {
SecureRandom secureRandom = new SecureRandom();
byte[] salt = new byte[16];
secureRandom.nextBytes(salt);
return salt;
}
private static byte[] generateFixedSalt16Byte() {
// ### security warning - never use this in production ###
byte[] salt = new byte[16]; // 16 x0's
return salt;
}
private static String base64Encoding(byte[] input) {
return Base64.getEncoder().encodeToString(input);
}
}
Related
I already have a database which contains a table named User, this table contains a table salt, password etc...
salt column has data with length 43
password column has data with 88 length
my database was created by symfony(ORM+FOSUserBundle) he use sha512.
I am trying to get a salt and a password , and store them into the database from desktop java application, so I tried this Class:
import com.google.common.io.BaseEncoding;
import org.slf4j.Logger;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import com.google.common.primitives.Bytes;
import org.slf4j.LoggerFactory;
public class SHA512 {
private static final Logger log = LoggerFactory.getLogger(SHA512.class);
private static final String ALGORITHM = "SHA-512";
private static final int ITERATIONS = 5000;
private static final int SALT_SIZE = 43;
/**
* Private constructor.
*/
private SHA512() {
}
public static void main(String[] args) {
String password = "0000";
try {
byte[] salt = generateSalt();
log.info("Password {}. hash algorithm {}, iterations {}, salt {}", password, ALGORITHM, ITERATIONS,
BaseEncoding.base64().encode(salt));
byte[] hash = calculateHash(password, salt);
boolean correct = verifyPassword(hash, password, salt);
log.info("Entered password is correct: {}", correct);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
log.error(ex.getMessage(), ex);
}
}
private static byte[] generateSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[SALT_SIZE];
random.nextBytes(salt);
return salt;
}
private static byte[] calculateHash(String password, byte[] salt) throws NoSuchAlgorithmException,
UnsupportedEncodingException {
MessageDigest md = MessageDigest.getInstance(ALGORITHM);
md.reset();
md.update(Bytes.concat(password.getBytes("UTF-8"), salt));
byte[] hash = md.digest();
for (int i = 0; i < ITERATIONS; i++) {
md.reset();
hash = md.digest(hash);
}
return hash;
}
private static boolean verifyPassword(byte[] originalHash, String password, byte[] salt) throws
NoSuchAlgorithmException, UnsupportedEncodingException {
byte[] comparisonHash = calculateHash(password, salt);
log.info("hash 1: {}", BaseEncoding.base64().encode(originalHash));
log.info("hash 2: {}", BaseEncoding.base64().encode(comparisonHash));
return comparePasswords(originalHash, comparisonHash);
}
/**
* Compares the two byte arrays in length-constant time using XOR.
*
* #param originalHash The original password hash
* #param comparisonHash The comparison password hash
* #return True if both match, false otherwise
*/
private static boolean comparePasswords(byte[] originalHash, byte[] comparisonHash) {
int diff = originalHash.length ^ comparisonHash.length;
for (int i = 0; i < originalHash.length && i < comparisonHash.length; i++) {
diff |= originalHash[i] ^ comparisonHash[i];
}
return diff == 0;
}
}
I need a salt with lenght 43, But BaseEncoding.base64().encode(salt) output salt with lenght equal 60.
I modified the SALT_SIZE to 30 and BaseEncoding.base64().encode(salt) output a salt with length equal to 40 but, when I add a user with that salt and with the hash generated, I can't Login with this user from my web application(already said that FOSUserBundle controls authentication and encode/decode algorithm)
If I am reading this right, you have an error in your digest encoder implementation.
Reference points:
MessageDigestPasswordEncoder.php
BasePasswordEncoder.php
When you request to manually generate digest, there are basically two steps:
Hash the concat: password + { + salt + }
For each additional iteration: hash the concat of previous digest + salt (note that this step does not add { or } chars)
So, I see two issues in you implementation:
It looks to me that you have not included those { and } in iteration #0
You have not included the salt in any of later iteration
Hope this helps a bit...
I've been investigating a bit about Java String encryption techniques and unfortunately I haven't find any good tutorial how to hash String with SHA-512 in Java; I read a few blogs about MD5 and Base64, but they are not as secure as I'd like to (actually, Base64 is not an encryption technique), so I prefer SHA-512.
you can use this for SHA-512 (Not a good choice for password hashing).
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public String get_SHA_512_SecurePassword(String passwordToHash, String salt){
String generatedPassword = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(salt.getBytes(StandardCharsets.UTF_8));
byte[] bytes = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for(int i=0; i< bytes.length ;i++){
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
generatedPassword = sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return generatedPassword;
}
Please stop using hash functions to encode passwords! They do not provide the protection you need. Instead, you should be using an algorithm like PBKDF2, bcrypt, or scrypt.
References:
http://blog.tjll.net/please-stop-hashing-passwords/
http://security.blogoverflow.com/2011/11/why-passwords-should-be-hashed/
https://crackstation.net/hashing-security.htm
http://www.sitepoint.com/risks-challenges-password-hashing/
http://security.blogoverflow.com/2013/09/about-secure-password-hashing/
Using Guava:
Hashing.sha512().hashString(s, StandardCharsets.UTF_8).toString()
Use Apache Commons Crypt, it features SHA-512 based crypt() functions that generate salted hashes that are even compatible to libc's crypt and thus usable in PHP/Perl/Python/C and most databases, too.
https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/digest/Crypt.html#Crypt%28%29
you could use this to hash a password in java if you want to.
public static boolean isHashMatch(String password, // the password you want to check.
String saltedHash, // the salted hash you want to check your password against.
String hashAlgorithm, // the algorithm you want to use.
String delimiter) throws NoSuchAlgorithmException // the delimiter that has been used to delimit the salt and the hash.
{
// get the salt from the salted hash and decode it into a byte[].
byte[] salt = Base64.getDecoder()
.decode(saltedHash.split(delimiter)[0]);
// compute a new salted hash based on the provided password and salt.
String pw_saltedHash = computeSaltedBase64Hash(password,
salt,
hashAlgorithm,
delimiter);
// check if the provided salted hash matches the salted hash we computed from the password and salt.
return saltedHash.equals(pw_saltedHash);
}
public static String computeSaltedBase64Hash(String password, // the password you want to hash
String hashAlgorithm, // the algorithm you want to use.
String delimiter) throws NoSuchAlgorithmException // the delimiter that will be used to delimit the salt and the hash.
{
// compute the salted hash with a random salt.
return computeSaltedBase64Hash(password, null, hashAlgorithm, delimiter);
}
public static String computeSaltedBase64Hash(String password, // the password you want to hash
byte[] salt, // the salt you want to use (uses random salt if null).
String hashAlgorithm, // the algorithm you want to use.
String delimiter) throws NoSuchAlgorithmException // the delimiter that will be used to delimit the salt and the hash.
{
// transform the password string into a byte[]. we have to do this to work with it later.
byte[] passwordBytes = password.getBytes();
byte[] saltBytes;
if(salt != null)
{
saltBytes = salt;
}
else
{
// if null has been provided as salt parameter create a new random salt.
saltBytes = new byte[64];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(saltBytes);
}
// MessageDigest converts our password and salt into a hash.
MessageDigest messageDigest = MessageDigest.getInstance(hashAlgorithm);
// concatenate the salt byte[] and the password byte[].
byte[] saltAndPassword = concatArrays(saltBytes, passwordBytes);
// create the hash from our concatenated byte[].
byte[] saltedHash = messageDigest.digest(saltAndPassword);
// get java's base64 encoder for encoding.
Encoder base64Encoder = Base64.getEncoder();
// create a StringBuilder to build the result.
StringBuilder result = new StringBuilder();
result.append(base64Encoder.encodeToString(saltBytes)) // base64-encode the salt and append it.
.append(delimiter) // append the delimiter (watch out! don't use regex expressions as delimiter if you plan to use String.split() to isolate the salt!)
.append(base64Encoder.encodeToString(saltedHash)); // base64-encode the salted hash and append it.
// return a salt and salted hash combo.
return result.toString();
}
public static byte[] concatArrays(byte[]... arrays)
{
int concatLength = 0;
// get the actual length of all arrays and add it so we know how long our concatenated array has to be.
for(int i = 0; i< arrays.length; i++)
{
concatLength = concatLength + arrays[i].length;
}
// prepare our concatenated array which we're going to return later.
byte[] concatArray = new byte[concatLength];
// this index tells us where we write into our array.
int index = 0;
// concatenate the arrays.
for(int i = 0; i < arrays.length; i++)
{
for(int j = 0; j < arrays[i].length; j++)
{
concatArray[index] = arrays[i][j];
index++;
}
}
// return the concatenated arrays.
return concatArray;
}
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Hex;
public String getHashSHA512(String StringToHash, String salt){
String generatedPassword = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(salt.getBytes(StandardCharsets.UTF_8));
byte[] bytes = md.digest(StringToHash.getBytes(StandardCharsets.UTF_8));
generatedPassword = Hex.encodeHexString(bytes);
}
catch (NoSuchAlgorithmException e){
e.printStackTrace();
}
return generatedPassword;
}
It's not recommended to use hash functions for passwords though, newer alogrithms like bcrypt, or scrypt exist
With secure hashing combine 3 salt components (of 150 random characters each) to a individual user salt (user salt from the user database table, general salt in a database table (monthly change with cron job) and hide some salt in the application library). Align the for loop amount of the secure hash to your needs. See answer above for hashing method.
private static String generateSalt(int lenght){
String abcCapitals = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
String abcLowerCase = "abcdefghijklmnopqrstuvwxyz";
String numbers = "01234567890123456789";
String characters = "!##$%^&*!##$%%^^&*";
String total = abcCapitals + abcLowerCase + numbers + characters;
String response = "";
char letters[] = new char[lenght];
for (int i=0; i<lenght-1; i++){
Random r = new Random();
char letter = total.charAt(r.nextInt(total.length()));
letters[i] = letter;
}
response = Arrays.toString(letters).replaceAll("\\s+","");
response = response.replaceAll(",","");
return response;
}
private static String getHash(String passwordToHash, String salt){
String generatedPassword = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(salt.getBytes(StandardCharsets.UTF_8));
byte[] bytes = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for(int i=0; i< bytes.length ;i++){
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
generatedPassword = sb.toString();
}
catch (NoSuchAlgorithmException e){
System.out.println(e);
}
return generatedPassword;
}
public static String getSecureHash(String password, String salt){
String hash = getHash(password, salt);
for (int i=0; i<20000; i++){
hash = getHash(password, hash);
}
return hash;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
String salt = generateSalt(150);
String salt2 = generateSalt(150);
String salt3 = generateSalt(150);
String someString = "This is some string!";
String hash = getSecureHash(someString, salt + salt2 + salt3);
System.out.println(hash);
}
I wrote this simple Java program which encrypt a string and output the hex value of the iv, salt, derived key and cipher text.
public class tmp{
static Cipher encryptionCipher;
static String RANDOM_ALGORITHM = "SHA1PRNG";
static String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
static String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";
static String SECRET_KEY_ALGORITHM = "AES";
static int PBE_ITERATION_COUNT = 2048;
static String PROVIDER = "BC";
public static byte[] generateIv() {
try{
SecureRandom random;
random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] iv = new byte[16];
random.nextBytes(iv);
return iv;
} catch(Exception e){
return null; // Always must return something
}
}
public static byte[] generateSalt() {
try {SecureRandom random;
random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] salt = new byte[32];
random.nextBytes(salt);
return salt;
} catch(Exception e){
return null; // Always must return something
}
}
public static SecretKey getSecretKey(String password, byte[] salt){
try {
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, PBE_ITERATION_COUNT, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM, PROVIDER);
SecretKey tmp = factory.generateSecret(pbeKeySpec);
return new SecretKeySpec(tmp.getEncoded(), SECRET_KEY_ALGORITHM);
} catch(Exception e){
System.out.println(e); // Always must return something
return null;
}
}
public static String encrypt(String plaintext, Key key, byte[] iv) {
try {
AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(iv);
encryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
encryptionCipher.init(Cipher.ENCRYPT_MODE, key, ivParamSpec);
byte[] ciphertext = encryptionCipher.doFinal(plaintext.getBytes("UTF-8"));
String cipherHexString = DatatypeConverter.printHexBinary(ciphertext);
return cipherHexString;
}
catch (Exception e) {
System.out.println(e);
return null;
}
}
public static void main (String[] Args){
SecretKey key;
//sha512(ciao)
String encami = "This is a test pharse. Thanks!!";
String password = "a0c299b71a9e59d5ebb07917e70601a3570aa103e99a7bb65a58e780ec9077b1902d1dedb31b1457beda595fe4d71d779b6ca9cad476266cc07590e31d84b206";
byte[] iv = new byte[16];
byte[] salt = new byte[32];
iv = generateIv();
salt = generateSalt();
String ll = DatatypeConverter.printHexBinary(iv);
String lp = DatatypeConverter.printHexBinary(salt);
System.out.println(ll);
System.out.println(lp);
key = getSecretKey(password, salt);
byte tt[] = new byte[32];
tt = key.getEncoded();
String lo = DatatypeConverter.printHexBinary(tt);
System.out.println(lo);
String outenc = encrypt(encami, key, iv);
System.out.println(outenc);
}
}
In the following C program iv and salt are initialized with the values given by the above Java program. No padding needed since the length of the text is 32 bytes.
#include <stdio.h>
#include <gcrypt.h>
#include <stdlib.h>
#include <string.h>
int
main (void)
{
int i;
char *encami = "This is a test pharse. Thanks!!";
char *pwd = "a0c299b71a9e59d5ebb07917e70601a3570aa103e99a7bb65a58e780ec9077b1902d1dedb31b1457beda595fe4d71d779b6ca9cad476266cc07590e31d84b206";
unsigned char iv[] = {};
unsigned char salt[] = {};
int algo = gcry_cipher_map_name("aes256");
unsigned char *devkey = NULL;
unsigned char *enc_buf = NULL;
enc_buf = gcry_malloc(32);
devkey = gcry_malloc_secure (32);
gcry_cipher_hd_t hd;
gcry_cipher_open(&hd, algo, GCRY_CIPHER_MODE_CBC, 0);
gcry_kdf_derive (pwd, strlen(pwd)+1, GCRY_KDF_PBKDF2, GCRY_MD_SHA256, salt, 32, 2048, 32, devkey);
for (i=0; i<32; i++)
printf ("%02x", devkey[i]);
printf("\n");
gcry_cipher_setkey(hd, devkey, 32);
gcry_cipher_setiv(hd, iv, 16);
gcry_cipher_encrypt(hd, enc_buf, strlen(encami)+1, encami, strlen(encami)+1);
for (i=0; i<32; i++)
printf("%02x", enc_buf[i]);
printf("\n");
gcry_cipher_close(hd);
gcry_free(enc_buf);
gcry_free (devkey);
return 0;
}
My problem is that the derived key is not the same in those two programs. Why?
Is the bouncy castle deriving function not working in the same way as gcry_kdf_derive?
Thanks!
I've now looked into the PBEWithSHA256And256BitAES-CBC-BC algorithm in the BC provider, and found that it is not compatible with GCRY_KDF_PBKDF2. The gcrypt algorithm is PKCS#5 2.0 Scheme 2, whereas the BC one is actually implementing PKCS#12.
Actually, I've so far not found a named algorithm in the provider that matches the gcrypt one, however I was able to use the BC API directly to get matching results b/w them, as follows.
Bouncy Castle:
byte[] salt = new byte[8];
Arrays.fill(salt, (byte)1);
PBEParametersGenerator pGen = new PKCS5S2ParametersGenerator(new SHA256Digest());
pGen.init(Strings.toByteArray("password"), salt, 2048);
KeyParameter key = (KeyParameter)pGen.generateDerivedParameters(256);
System.out.println(Hex.toHexString(key.getKey()));
gcrypt:
unsigned char salt[8];
memset(salt, 1, 8);
unsigned char key[32];
gcry_kdf_derive("password", 8, GCRY_KDF_PBKDF2, GCRY_MD_SHA256, salt, 8, 2048, 32, key);
for (int i = 0; i < 32; ++i)
printf("%02x", key[i]);
printf("\n");
which both output:
4182537a153b1f0da1ccb57971787a42537e38dbf2b4aa3692baebb106fc02e8
You appear to be including a terminating NULL character in your count of 32 bytes (encami), which explains the differing outputs. The java version sees a 31-character input and provides a single PKCS#7-padded output block (PKCS#7 will pad the input with a single '1' byte). The C version is passed 32 bytes, including the final '0' byte. So the inputs are different.
I recommend you stop treating the NULL terminator as part of the input; instead apply PKCS#7 padding, as the Java version is doing. I'm not familiar with gcrypt, so I don't know what the typical method is for doing this, but PKCS#7 padding is a quite simple concept in any case.
I setup a program for a class I am taking on crypto. I will follow this with my code and another section for my variable differences. My goal is to decrypt the text for our homework. I do not want someone to decrypt this for me, but would like some help as to what is causing this within my code. When I decrypt CBC I get the correct output with no problem, though it does have some extra chars in it (this may be an issue with padding? I am not sure)
Then when I use the CTR with the correct changes it returns a bunch of garbage. Any help would be greatly appreciated.
Thank you,
CBC:
CBC key: 140b41b22a29beb4061bda66b6747e14
CBC Ciphertext 1:
4ca00ff4c898d61e1edbf1800618fb2828a226d160dad07883d04e008a7897ee2e4b7465d5290d0c0e6c6822236e1daafb94ffe0c5da05d9476be028ad7c1d81
CTR:
CTR key: 36f18357be4dbd77f050515c73fcf9f2
CTR Ciphertext 1:
69dda8455c7dd4254bf353b773304eec0ec7702330098ce7f7520d1cbbb20fc388d1b0adb5054dbd7370849dbf0b88d393f252e764f1f5f7ad97ef79d59ce29f5f51eeca32eabedd9afa9329
CBC Variables
String algorithm = "AES";
String mode = "CBC";
String padding = "PKCS5Padding";
byte[] ciphertextBytes = StringToByte("4ca00ff4c898d61e1edbf1800618fb2828a226d160dad07883d04e008a7897ee2e4b7465d5290d0c0e6c6822236e1daafb94ffe0c5da05d9476be028ad7c1d81");
byte[] keyBytes = StringToByte("140b41b22a29beb4061bda66b6747e14");
CTR Variables
String algorithm = "AES";
String mode = "CTR";
String padding = "NoPadding";
byte[] ciphertextBytes = StringToByte("770b80259ec33beb2561358a9f2dc617e46218c0a53cbeca695ae45faa8952aa0e311bde9d4e01726d3184c34451");
byte[] keyBytes = StringToByte("36f18357be4dbd77f050515c73fcf9f2");
Decrypt Main
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import static java.lang.Character.digit;
public class CryptoClass {
public static void main(String[] args) throws Exception {
byte[] decryptByte = Decrypt();
String hexString = ByteToHex(decryptByte);
StringBuilder decryptedString = HexToString(hexString);
System.out.println(decryptedString);
}
public static byte[] Decrypt() throws Exception {
//
String algorithm = "AES";
String mode = "CTR";
String padding = "NoPadding";
byte[] ciphertextBytes = StringToByte("770b80259ec33beb2561358a9f2dc617e46218c0a53cbeca695ae45faa8952aa0e311bde9d4e01726d3184c34451");
byte[] keyBytes = StringToByte("36f18357be4dbd77f050515c73fcf9f2");
IvParameterSpec ivParamSpec = null;
int ivSize = 16;
byte[] iv = new byte[ivSize];
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.nextBytes(iv);
ivParamSpec = new IvParameterSpec(iv);
SecretKey aesKey = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance(algorithm + "/" + mode + "/" + padding, "JsafeJCE");
cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParamSpec);
byte[] result = cipher.doFinal(ciphertextBytes);
return result;
}
//convert ByteArray to Hex String
public static String ByteToHex(byte[] byteArray) {
StringBuilder sb = new StringBuilder();
for (byte b : byteArray)
{
sb.append(String.format("%02X", b));
}
return sb.toString();
}
//convert String to ByteArray
private static byte[] StringToByte(String input) {
int length = input.length();
byte[] output = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
output[i / 2] = (byte) ((digit(input.charAt(i), 16) << 4) | digit(input.charAt(i+1), 16));
}
return output;
}
//changes a hex string into plain text
public static StringBuilder HexToString(String hex) throws Exception {
StringBuilder output = new StringBuilder();
for (int i = 0; i < hex.length(); i+=2) {
String str = hex.substring(i, i+2);
output.append((char)Integer.parseInt(str, 16));
}
return output;
}
}
*Edit method for solution - instead of a random IV I pulled the IV from the first 16 bits of the ciphertext. In the assignment it stated that this was the case, for some reason I glossed over it when I looked through it the first time.
public static byte[] Decrypt() throws Exception {
String algorithm = "AES";
String mode = "CTR";
String padding = "NoPadding";
byte[] ciphertextBytes = StringToByte("0ec7702330098ce7f7520d1cbbb20fc388d1b0adb5054dbd7370849dbf0b88d393f252e764f1f5f7ad97ef79d59ce29f5f51eeca32eabedd9afa9329");
byte[] keyBytes = StringToByte("36f18357be4dbd77f050515c73fcf9f2");
//int ivSize = 16;
//byte[] iv = new byte[ivSize];
//SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
//secureRandom.nextBytes(iv);
byte[] ivParamSpecTMP = StringToByte("69dda8455c7dd4254bf353b773304eec");
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivParamSpecTMP);
SecretKey aesKey = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance(algorithm + "/" + mode + "/" + padding, "JsafeJCE");
cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
byte[] result = cipher.doFinal(ciphertextBytes);
return result;
The trick is that you must send the IV (in plain text) to the receiver. If you randomly generate the IV before decryption you will get garbage by definition. Random IV's should only be generated before encryption.
Standard practice is for the sender to prefix the IV to the ciphertext. The receiver uses the first 16 bytes as an IV and the rest as the actual ciphertext.
I am new to encryption and decryption. I was given a PSKC file and asked for decryption. I was given the password for decryption. The PSKC file doenot have initialization vector value.
I wrote the code trying to decrypt it. But i am unsuccessful in achieving the outcome.
below is the PSKC file example
<?xml version="1.0"?>
<pskc:KeyContainer xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:pkcs5="http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" xmlns:xenc11="http://www.w3.org/2009/xmlenc11#" xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc">
<pskc:EncryptionKey>
<xenc11:DerivedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" xmlns:pkcs5="http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#" xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xenc11="http://www.w3.org/2009/xmlenc11#">
<xenc11:KeyDerivationMethod Algorithm="http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2">
<pkcs5:PBKDF2-params xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" xmlns:xenc11="http://www.w3.org/2009/xmlenc11#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:pkcs5="http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#">
<Salt>
<Specified>EW0h0yUcDX72WU9UiKiCwDpXsJg=</Specified>
</Salt>
<IterationCount>128</IterationCount>
<KeyLength>16</KeyLength>
<PRF />
</pkcs5:PBKDF2-params>
</xenc11:KeyDerivationMethod>
<xenc:ReferenceList>
<xenc:DataReference URI="#ED" />
</xenc:ReferenceList>
<xenc11:MasterKeyName>Passphrase1</xenc11:MasterKeyName>
</xenc11:DerivedKey>
</pskc:EncryptionKey>
<pskc:MACMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1">
<pskc:MACKey>
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />
<xenc:CipherData>
<xenc:CipherValue>jq/NdikC7AZf0Z+HEL5NrCICV8XW+ttzl/8687hVGHceoyJAaFws+111plQH6Mlg</xenc:CipherValue>
</xenc:CipherData>
</pskc:MACKey>
</pskc:MACMethod>
<pskc:KeyPackage>
<pskc:DeviceInfo>
<pskc:Manufacturer>Gemalto</pskc:Manufacturer>
<pskc:SerialNo>GAKT000047A5</pskc:SerialNo>
</pskc:DeviceInfo>
<pskc:CryptoModuleInfo>
<pskc:Id>CM_ID_007</pskc:Id>
</pskc:CryptoModuleInfo>
<pskc:Key Id="GAKT000047A5" Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:totp">
<pskc:Issuer>Issuer0</pskc:Issuer>
<pskc:AlgorithmParameters>
<pskc:ResponseFormat Encoding="DECIMAL" Length="6" />
</pskc:AlgorithmParameters>
<pskc:Data>
<pskc:Secret>
<pskc:EncryptedValue>
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />
<xenc:CipherData>
<xenc:CipherValue>pM7VB/KomPjq2cKaxPr5cKT1tUZN5tGMI+u1XKJTG1la+ThraPpLKlL2plKk6vQE</xenc:CipherValue>
</xenc:CipherData>
</pskc:EncryptedValue>
<pskc:ValueMAC>lbu+9OcLArnj6mS7KYOKDa4zRU0=</pskc:ValueMAC>
</pskc:Secret>
<pskc:Time>
<pskc:PlainValue>0</pskc:PlainValue>
</pskc:Time>
<pskc:TimeInterval>
<pskc:PlainValue>30</pskc:PlainValue>
</pskc:TimeInterval>
</pskc:Data>
</pskc:Key>
</pskc:KeyPackage>
</pskc:KeyContainer>
below is the java code which i have written for decryption.
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;
public class test {
/**
* #param args
*/
public static void main(String[] args) {
test te = new test();
try {
te.decryptSeedValue();
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println(e.getMessage());
e.printStackTrace();
}
// TODO Auto-generated method stub
}
public static HashMap decryptSeedValue()throws Exception{
String password = "G?20R+I+3-/UcWIN";
String pbesalt ="EW0h0yUcDX72WU9UiKiCwDpXsJg=";
String iv = "aaaaaaaaaaaaaaaaaaaaaaaa";
int iteration = 128;
String value = "pM7VB/KomPjq2cKaxPr5cKT1tUZN5tGMI+u1XKJTG1la+ThraPpLKlL2plKk6vQE";
String valueDigest = "lbu+9OcLArnj6mS7KYOKDa4zRU0=";
byte[] cipherText =null;
//some parameters need to decode from Base64 to byte[]
byte[] data = base64Decode(value.getBytes());
//System.out.println("data(hex string) = " + HexBin.encode(data));//debug
byte[] salt = base64Decode(pbesalt.getBytes());
//System.out.println("salt(hex string) = " + HexBin.encode(salt));//debug
byte[] initVec = base64Decode(iv.getBytes());
//System.out.println("iv(hex string) = " + HexBin.encode(initVec));//debug
//perform PBE key generation and AES/CBC/PKCS5Padding decrpyption
HashMap hs = myFunction(data, password, initVec, salt, iteration);
String seedValue = (String)hs.get("DECRYPTED_SEED_VALUE");
byte[] temp = (byte[])hs.get("HASH_OUTPUT");
//System.out.println("hashed output(hex string) = " + HexBin.encode(temp));//debug
//perform Base64 Encode
byte[] out = base64Encode(temp);
String output = new String((out));
System.out.println("output = "+output);
System.out.println("valueD = "+valueDigest);
//System.out.println("hashed output(base64) = " + output);
//compare the result
if(output.equals(valueDigest)){
System.out.println("Hash verification successful for:-->" );
System.out.println("\n");
//hs.put("SEED_VALUE", HexBin.encode(temp));
hs.put("SEED_VALUE", seedValue);
return hs;
}
else{
System.out.println("Hash verification failed for :-->");
return null;
}
}
public static HashMap myFunction(byte[] data, String password, byte[] initVec,
byte[] salt, int iteration) throws Exception{
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator();
byte[] pBytes = password.getBytes();
generator.init(pBytes, salt, iteration);
int keysize = 128;//fixed at AES key of 16 bytes
int ivsize = initVec.length;
ParametersWithIV params = (ParametersWithIV) generator.generateDerivedParameters(keysize, ivsize);
KeyParameter keyParam = (KeyParameter) params.getParameters();
//System.out.println("derived key = " + HexBin.encode(keyParam.getKey()));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec paramSpec = new IvParameterSpec(initVec);
SecretKeySpec key = new SecretKeySpec(keyParam.getKey(), "AES");
cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
//perform decryption
byte[] secret = cipher.doFinal(data);
//display the 20 bytes secret of the token
//System.out.println("token secret(hex string) = " + HexBin.encode(secret));
//perform HMAC-SHA-1
byte[] output = hmac_sha1(secret, keyParam.getKey());
HashMap hs = new HashMap();
hs.put("ENCRYPTION_KEY", HexBin.encode(keyParam.getKey()));
hs.put("HASH_OUTPUT", output);
hs.put("DECRYPTED_SEED_VALUE", HexBin.encode(secret));
return hs;
}
public static byte[] base64Encode(byte[] passwordBytes) throws NoSuchAlgorithmException {
Base64 base64 = new Base64();
byte[] hashBytes2 = base64.encode(passwordBytes);
return hashBytes2;
}
public static byte[] base64Decode(byte[] passwordBytes) throws NoSuchAlgorithmException {
Base64 base64 = new Base64();
byte[] hashBytes2 = base64.decode(passwordBytes);
return hashBytes2;
}
public static byte[] hmac_sha1(byte[] dataByte, byte[] keyByte) throws Exception{
Mac hmacSha1;
hmacSha1 = Mac.getInstance("HmacSHA1");
SecretKeySpec macKey = new SecretKeySpec(keyByte, "HmacSHA1");
hmacSha1.init(macKey);
byte[] result = hmacSha1.doFinal(dataByte);
return result;
}
/**
* Convert a byte array of 8 bit characters into a String.
*
* #param bytes the array containing the characters
* #param length the number of bytes to process
* #return a String representation of bytes
*/
private static String toString(
byte[] bytes,
int length)
{
char[] chars = new char[length];
for (int i = 0; i != chars.length; i++)
{
chars[i] = (char)(bytes[i] & 0xff);
}
return new String(chars);
}
}
it doesn't throw any exception, but it prints "Hash verification failed for" which is defined in my code when decryption fails.
Can some one please help me out.
As per the pskc standard http://www.rfc-editor.org/rfc/rfc6030.txt the IV is prepended to the ciphervalue. This is aes128, so it'll be the first 16 bytes once it's been base64 decoded.
Adding onto what bcharlton is describing; what you are not doing is check the hmac_sha1 for the encrypted data (which has the iv prepended in encrypted form), using the MACKey described in the xml document.
With AES-128 CBC the initialization vector is explicitly defined, and since there is no verification built into it, it uses HMAC for it.
So given your example the following will work:
public static HashMap decryptSeedValue() throws Exception
{
String password = "G?20R+I+3-/UcWIN";
String pbesalt = "EW0h0yUcDX72WU9UiKiCwDpXsJg=";
String iv = "aaaaaaaaaaaaaaaaaaaaaaaa";
int iteration = 128;
String value = "pM7VB/KomPjq2cKaxPr5cKT1tUZN5tGMI+u1XKJTG1la+ThraPpLKlL2plKk6vQE";
String valueDigest = "lbu+9OcLArnj6mS7KYOKDa4zRU0=";
//YOU NEED THIS GUY BELOW TO VERIFY
String macKey = "jq/NdikC7AZf0Z+HEL5NrCICV8XW+ttzl/8687hVGHceoyJAaFws+111plQH6Mlg";
byte[] cipherText = null;
//some parameters need to decode from Base64 to byte[]
byte[] data = base64Decode(value.getBytes());
//System.out.println("data(hex string) = " + HexBin.encode(data));//debug
byte[] salt = base64Decode(pbesalt.getBytes());
//System.out.println("salt(hex string) = " + HexBin.encode(salt));//debug
byte[] initVec = base64Decode(iv.getBytes());
//System.out.println("iv(hex string) = " + HexBin.encode(initVec));//debug
//perform PBE key generation and AES/CBC/PKCS5Padding decrpyption
HashMap hs = myFunction(data, password, base64Decode(macKey.getBytes()), salt, iteration);
String seedValue = (String) hs.get("DECRYPTED_SEED_VALUE");
byte[] temp = (byte[]) hs.get("HASH_OUTPUT");
//System.out.println("hashed output(hex string) = " + HexBin.encode(temp));//debug
//perform Base64 Encode
byte[] out = base64Encode(temp);
String output = new String((out));
System.out.println("output = " + output);
System.out.println("valueD = " + valueDigest);
//System.out.println("hashed output(base64) = " + output);
//compare the result
if (output.equals(valueDigest)) {
System.out.println("Hash verification successful for:-->");
System.out.println("\n");
//hs.put("SEED_VALUE", HexBin.encode(temp));
hs.put("SEED_VALUE", seedValue);
return hs;
} else {
System.out.println("Hash verification failed for :-->");
return null;
}
}
public static HashMap myFunction(byte[] data, String password, byte[] macData,
byte[] salt, int iteration) throws Exception
{
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator();
byte[] pBytes = password.getBytes();
generator.init(pBytes, salt, iteration);
byte[] iv = new byte[16];
int ivsize = iv.length;
byte[] encryptedData = new byte[data.length - ivsize];
System.arraycopy(data, 0, iv, 0, iv.length);
System.arraycopy(data, ivsize, encryptedData, 0, encryptedData.length);
byte[] maciv = new byte[16];
byte[] encryptedMac = new byte[macData.length - maciv.length];
System.arraycopy(macData, 0, maciv, 0, maciv.length);
System.arraycopy(macData, maciv.length, encryptedMac, 0, encryptedMac.length);
int keysize = 128;//fixed at AES key of 16 bytes
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iteration, keysize);
SecretKey tmp = factory.generateSecret(spec);
SecretKey key = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
byte[] decryptedData = dcipher.doFinal(encryptedData);
// decryptedData is your token value!
dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(maciv));
byte[] decryptedMac = dcipher.doFinal(encryptedMac);
//display the 20 bytes secret of the token
//System.out.println("token secret(hex string) = " + HexBin.encode(secret));
//perform HMAC-SHA-1
//Use the decrypted MAC key here for hashing!
byte[] output = hmac_sha1(data, decryptedMac);
HashMap hs = new HashMap();
hs.put("ENCRYPTION_KEY", password);
hs.put("HASH_OUTPUT", output);
hs.put("DECRYPTED_SEED_VALUE", HexBin.encode(decryptedData));
return hs;
}
Keep in mind that as https://www.rfc-editor.org/rfc/rfc6030#section-6.2 describes, a different iv can be used for the MAC and the token key.