I am trying to write the same algorithm in c# of what Liferay uses PBKDF2WithHmacSHA1/160/128000 as we have hashed passwords migrated to different platforms. however the hashed passwords values are coming as different in C# Not too sure where I am doing wrong. I am not very much familiar with Java.
parameters passed in Java are
String algorithm = "PBKDF2WithHmacSHA1/160/128000"
String plainTextPassword = "!!Fres1966"
String encryptedPassword = null
hash value in Java = "AAAAoAAB9ACnaq7ogwbkgRWfNcsfFh2DEMKmfl7JAqR8SAqk" hash value in c# = "FAAAAAD0AQAAAAAAAAAAAFJZUX3EUFdhFHeMZLSdlo9RxsxO"
There is one line of code which I have not been able to replicate is
BigEndianCodec.putLong(_saltBytes, 0, SecureRandomUtil.nextLong())
not too sure what this line of code is doing and how can I replicate the similar logic in c#
Code in Java-------------
public class PBKDF2PasswordEncryptor
extends BasePasswordEncryptor implements PasswordEncryptor {
@Override
public String[] getSupportedAlgorithmTypes() {
return new String[] {PasswordEncryptorUtil.TYPE_PBKDF2};
}
@Override
protected String doEncrypt(
String algorithm, String plainTextPassword,
String encryptedPassword)
throws PwdEncryptorException {
try {
PBKDF2EncryptionConfiguration pbkdf2EncryptionConfiguration =
new PBKDF2EncryptionConfiguration();
pbkdf2EncryptionConfiguration.configure(
algorithm, encryptedPassword);
byte[] saltBytes = pbkdf2EncryptionConfiguration.getSaltBytes();
PBEKeySpec pbeKeySpec = new PBEKeySpec(
plainTextPassword.toCharArray(), saltBytes,
pbkdf2EncryptionConfiguration.getRounds(),
pbkdf2EncryptionConfiguration.getKeySize());
String algorithmName = algorithm;
int index = algorithm.indexOf(CharPool.SLASH);
if (index > -1) {
algorithmName = algorithm.substring(0, index);
}
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(
algorithmName);
SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
byte[] secretKeyBytes = secretKey.getEncoded();
ByteBuffer byteBuffer = ByteBuffer.allocate(
2 * 4 + saltBytes.length + secretKeyBytes.length);
byteBuffer.putInt(pbkdf2EncryptionConfiguration.getKeySize());
byteBuffer.putInt(pbkdf2EncryptionConfiguration.getRounds());
byteBuffer.put(saltBytes);
byteBuffer.put(secretKeyBytes);
return Base64.encode(byteBuffer.array());
}
catch (Exception e) {
throw new PwdEncryptorException(e.getMessage(), e);
}
}
private static final int _KEY_SIZE = 160;
private static final int _ROUNDS = 128000;
private static final int _SALT_BYTES_LENGTH = 8;
private static Pattern _pattern = Pattern.compile(
"^.*/?([0-9]+)?/([0-9]+)$");
private class PBKDF2EncryptionConfiguration {
public void configure(String algorithm, String encryptedPassword)
throws PwdEncryptorException {
if (Validator.isNull(encryptedPassword)) {
Matcher matcher = _pattern.matcher(algorithm);
if (matcher.matches()) {
_keySize = GetterUtil.getInteger(
matcher.group(1), _KEY_SIZE);
_rounds = GetterUtil.getInteger(matcher.group(2), _ROUNDS);
}
BigEndianCodec.putLong(
_saltBytes, 0, SecureRandomUtil.nextLong());
}
else {
byte[] bytes = new byte[16];
try {
byte[] encryptedPasswordBytes = Base64.decode(
encryptedPassword);
System.arraycopy(
encryptedPasswordBytes, 0, bytes, 0, bytes.length);
}
catch (Exception e) {
throw new PwdEncryptorException(
"Unable to extract salt from encrypted password " +
e.getMessage(),
e);
}
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
_keySize = byteBuffer.getInt();
_rounds = byteBuffer.getInt();
byteBuffer.get(_saltBytes);
}
}
public int getKeySize() {
return _keySize;
}
public int getRounds() {
return _rounds;
}
public byte[] getSaltBytes() {
return _saltBytes;
}
private int _keySize = _KEY_SIZE;
private int _rounds = _ROUNDS;
private byte[] _saltBytes = new byte[_SALT_BYTES_LENGTH];
}
}
public string HasPassword(string password)
{
byte[] salt = new byte[8];
Rfc2898DeriveBytes Rfcbytes = new Rfc2898DeriveBytes(password, salt,
128000);
byte[] key = Rfcbytes.GetBytes(160/8);
var ms = new MemoryStream(8+salt.Length+key.Length);
using (BinaryWriter writer = new BinaryWriter(ms))
{
writer.Write(160/8);
writer.Write(128000);
writer.Write(salt);
writer.Write(key);
}
byte[] bytes = ms.ToArray();
string finalhash = Convert.ToBase64String(bytes);
return finalhash;
}
BigEndianCodec.putLong(_saltBytes, 0, SecureRandomUtil.nextLong())
Seems to be "Generate a random 8-byte value and write it into an array (in Big Endian order; but it's random, so that doesn't matter)".
So you'd want
using (RandomNumberGenerator csprng = RandomNumberGenerator.Create())
{
csprng.GetBytes(salt);
}
Between allocating salt
and passing it to Rfc2898DeriveBytes
' ctor.