C# Code looks like that (can't change it as it's in a client's system).
namespace Common {
public static class EncryptionHelper
{
private const string cryptoKey = "password";
// The Initialization Vector for the DES encryption routine
private static readonly byte[] IV = new byte[8] { 240, 3, 45, 29, 0, 76, 173, 59 };
/// <summary>
/// Encrypts provided string parameter
/// </summary>
public static string Encrypt(string s)
{
string result = string.Empty;
byte[] buffer = Encoding.ASCII.GetBytes(s);
byte[] k = Encoding.ASCII.GetBytes(cryptoKey);
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
MD5CryptoServiceProvider MD5 = new MD5CryptoServiceProvider();
des.Key = MD5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(cryptoKey));
des.IV = IV;
result = Convert.ToBase64String(des.CreateEncryptor().TransformFinalBlock(buffer, 0, buffer.Length));
return result;
}
}
}
I found client took this class from here: http://johnnycoder.com/blog/2008/07/03/c-encryption-decryption-helper-class/
I'm not very familiar with C# and I need to Decrypt string in PHP encrypted with
this code.
When I do "md5($key, true)" I don't get the same result as "MD5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(cryptoKey));", not sure why.
How to convert "byte[] IV" to PHP string ?
Any help would be appreciated.
Thank you.
Managed to get it working:
class Crypter
{
/**
*
* Encryption key
*
* #var
*/
protected $key;
/**
*
* Encryption vector
*
* #var
*/
protected $iv;
public function __construct()
{
$this->key = config('auth.triple_des_key');
$this->iv = implode(array_map("chr", config('auth.triple_des_iv')));
}
/**
*
* Decrypts string using tripleDES method.
*
* #param $input String
* #return String
*/
public function decryptTripleDES($input)
{
$td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CBC, '');
$encryptedData = base64_decode($input);
$key = iconv('utf-8', 'us-ascii//TRANSLIT', $this->key);
$key = md5($key, true);
$key .= substr($key, 0, 8);
mcrypt_generic_init($td, $key, $this->iv);
$decryptedData = mdecrypt_generic($td, $encryptedData);
mcrypt_generic_deinit($td);
//remove the padding text
$block = mcrypt_get_block_size("tripledes", "cbc");
$packing = ord($decryptedData{strlen($decryptedData) - 1});
if ($packing and ($packing < $block)) {
for ($P = strlen($decryptedData) - 1; $P >= strlen($decryptedData) - $packing; $P--) {
if (ord($decryptedData[$P]) != $packing) {
$packing = 0;
}
}
}
$decryptedData = substr($decryptedData, 0, strlen($decryptedData) - $packing);
return $decryptedData;
}
}
Related
I have a .net framework 4.5.2 project in which AES 256/GCM/NO padding implementation has to be performed. I found out that framework doesn't support GCM directly hence I tried using BouncyCastle. While passing the params it looks like something is wrong being passed to initialization. Also I wanted to make the Keysize as 256 bit . I took this page as reference
Below is the code which I'm trying :
public class BouncyCastleCustom:ICipherParameters
{
private const string ALGORITHM = "AES";
private const byte AesIvSize = 16;
private const byte GcmTagSize = 16; // in bytes
private readonly CipherMode _cipherMode = CipherMode.GCM;
//private const string _cipherMode = "GCM";
private readonly string _algorithm = ALGORITHM;
public string Encrypt(string plainText, byte[] key)
{
var random = new SecureRandom();
var iv = random.GenerateSeed(AesIvSize);
var keyParameters = CreateKeyParameters(key, iv, GcmTagSize * 8);
var cipher = CipherUtilities.GetCipher(_algorithm);
cipher.Init(true, keyParameters);/*System.ArgumentException: 'invalid parameter passed to AES init - Org.BouncyCastle.Crypto.Parameters.AeadParameters'*/
var plainTextData = Encoding.UTF8.GetBytes(plainText);
var cipherText = cipher.DoFinal(plainTextData);
return PackCipherData(cipherText, iv);
}
public string Decrypt(string cipherText, byte[] key)
{
var (encryptedBytes, iv, tagSize) = UnpackCipherData(cipherText);
var keyParameters = CreateKeyParameters(key, iv, tagSize * 8);
var cipher = CipherUtilities.GetCipher(_algorithm);
cipher.Init(false, keyParameters);
var decryptedData = cipher.DoFinal(encryptedBytes);
return Encoding.UTF8.GetString(decryptedData);
}
private ICipherParameters CreateKeyParameters(byte[] key, byte[] iv, int macSize)
{
var keyParameter = new KeyParameter(key);
if (_cipherMode == CipherMode.CBC)
{
return new ParametersWithIV(keyParameter, iv);
}
else if (_cipherMode == CipherMode.GCM)
{
return new AeadParameters(keyParameter, macSize, iv);
}
throw new Exception("Unsupported cipher mode");
}
private string PackCipherData(byte[] encryptedBytes, byte[] iv)
{
var dataSize = encryptedBytes.Length + iv.Length + 1;
if (_cipherMode == CipherMode.GCM)
dataSize += 1;
var index = 0;
var data = new byte[dataSize];
data[index] = AesIvSize;
index += 1;
if (_cipherMode == CipherMode.GCM)
{
data[index] = GcmTagSize;
index += 1;
}
Array.Copy(iv, 0, data, index, iv.Length);
index += iv.Length;
Array.Copy(encryptedBytes, 0, data, index, encryptedBytes.Length);
return Convert.ToBase64String(data);
}
private (byte[], byte[], byte) UnpackCipherData(string cipherText)
{
var index = 0;
var cipherData = Convert.FromBase64String(cipherText);
byte ivSize = cipherData[index];
index += 1;
byte tagSize = 0;
if (_cipherMode == CipherMode.GCM)
{
tagSize = cipherData[index];
index += 1;
}
byte[] iv = new byte[ivSize];
Array.Copy(cipherData, index, iv, 0, ivSize);
index += ivSize;
byte[] encryptedBytes = new byte[cipherData.Length - index];
Array.Copy(cipherData, index, encryptedBytes, 0, encryptedBytes.Length);
return (encryptedBytes, iv, tagSize);
}
public enum CipherMode //cannot access BouncyCastle inbuilt CipherMode
{
CBC,
GCM
}
}
static void Main(string[] args)
{
BouncyCastleCustom aes = new BouncyCastleCustom();
var encrypted = aes.Encrypt("testDemo", Encoding.UTF8.GetBytes("mysmallkey1234551298765134567890"));
Console.WriteLine("Encrypted testDemo: " + encrypted);
string decrypted = aes.Decrypt(encrypted, Encoding.UTF8.GetBytes("mysmallkey1234551298765134567890"));
Console.WriteLine("Decrypted: " + decrypted);
}
I need to transform this encryption function developed in C# to PHP. Thank you in advance.
private static byte[] Encrypt3DES(string codigoCliente, byte[] key)
{
var codigoClienteBytes = System.Text.Encoding.UTF8.GetBytes(codigoCliente);
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
byte[] SALT = new byte[8] { 0, 0, 0, 0, 0, 0, 0, 0 };
tdes.BlockSize = 64;
tdes.KeySize = 192;
tdes.Mode = CipherMode.CBC;
tdes.Padding = PaddingMode.Zeros;
tdes.IV = SALT;
tdes.Key = key;
var cTransform = tdes.CreateEncryptor();
byte[] resultArray = cTransform.TransformFinalBlock(codigoClienteBytes, 0, codigoClienteBytes.Length);
tdes.Clear();
return resultArray;
}
I've tried something like this in PHP but the string result is the same but not the same length.
function encrypt_3DES($message, $key)
{
// Se cifra
$l = ceil(strlen($message) / 8) * 8;
return encodeBase64(
openssl_encrypt($message . str_repeat("\0", $l - strlen($message)),
'des-ede3-cbc',
$key,
OPENSSL_RAW_DATA,
"\0\0\0\0\0\0\0\0")
);
}
You were very nearby to get the correct ciphertext in PHP.
Using below code will generate the same result as your C# function.
Security warning: 3DES (Triple DES) is outdated and should be substituted with modern algorithms like AES.
code:
function encrypt_3DES($message, $key)
{
$method = 'des-ede3-cbc';
if (strlen($message) % 8) {
$message = str_pad($message, strlen($message) + 8 - strlen($message) % 8, "\0");
}
$iv = "\0\0\0\0\0\0\0\0";
$encrypted = openssl_encrypt($message, $method, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv); //Force zero padding.
return base64_encode($encrypted);
}
Currently I am trying to implement a save function for my RSA key with the help of bouncycastle. I am running into problems if I try to save my public or private key encrypted and load it afterwards.
As a little example here the original public key:
305C300D06092A864886F70D0101010500034B00304802410096B4751049165D1E046063EA22E8FFA0F90AE1DD997A3876DA5F79C7DE97951F009AC9ACA3EB91114F8A32C04F48293B6665CD6DD5C406C81CD13270A2AB61130203010001
What I get after loading it (it adds 4 zeroes, bigger key means more zeroes added):
305C300D06092A864886F70D0101010500034B00304802410096B4751049165D1E046063EA22E8FFA0F90AE1DD997A3876DA5F79C7DE97951F009AC9ACA3EB91114F8A32C04F48293B6665CD6DD5C406C81CD13270A2AB611302030100010000
I found out it has something to do with my implementation of the symmetric encryption and the padding used there. Normal text no matter how long it is just works fine without extra data getting added.
This is the code I am using for my AES encryption:
Encryption
byte[] outputBytes = new byte[0];
AesEngine aesengine = new AesEngine();
CbcBlockCipher aesblockCipher = new CbcBlockCipher(aesengine);
PaddedBufferedBlockCipher aescipher = new PaddedBufferedBlockCipher(aesblockCipher);
KeyParameter aeskeyParameter = new KeyParameter(Hash.HashDataBlock(password, Hash.HashAlgorithm.SHA3).Bytes);
aescipher.Init(true, aeskeyParameter);
outputBytes = new byte[aescipher.GetOutputSize(inputBytes.Bytes.Length)];
int aeslength = aescipher.ProcessBytes(inputBytes.Bytes, outputBytes, 0);
aescipher.DoFinal(outputBytes, aeslength);
Decryption
byte[] inputBytes = input.Bytes;
byte[] outputBytes = new byte[0];
AesEngine aesengine = new AesEngine();
CbcBlockCipher aesblockCipher = new CbcBlockCipher(aesengine);
PaddedBufferedBlockCipher aescipher = new PaddedBufferedBlockCipher(aesblockCipher);
KeyParameter aeskeyParameter = new KeyParameter(Hash.HashDataBlock(password, Hash.HashAlgorithm.SHA3).Bytes);
aescipher.Init(false, aeskeyParameter);
outputBytes = new byte[aescipher.GetOutputSize(inputBytes.Length)];
int aeslength = aescipher.ProcessBytes(inputBytes, outputBytes, 0);
aescipher.DoFinal(outputBytes, aeslength);
My Functions to save and load the keys. The DataBlock class just converts data to needed formats like UTF8, Base64 or just byte arrays:
public static void SaveKeyEncrypted(DataBlock key, string path, DataBlock password)
{
StreamWriter sw = new StreamWriter(path);
DataBlock encrypted = SymmetricEncryption.Encrypt(key, password, SymmetricEncryption.SymmetricAlgorithms.AES);
sw.Write(encrypted.Base64);
sw.Close();
}
public static DataBlock ReadKeyEncrypted(string path, DataBlock password)
{
StreamReader sr = new StreamReader(path);
DataBlock readData = new DataBlock(sr.ReadLine(), DataBlock.DataType.Base64);
sr.Close();
return SymmetricEncryption.Decrypt(readData, password, SymmetricEncryption.SymmetricAlgorithms.AES);
}
For reproduction my other code that has to do with this problem:
public class DataBlock
{
private byte[] _data;
public DataBlock()
{
this._data = new byte[0];
}
public enum DataType
{
UTF8,
UTF7,
UTF32,
ASCII,
Unicode,
Hex,
Base64,
Base32
}
public DataBlock(string data, DataType dataType) : this()
{
switch (dataType)
{
case DataType.UTF8:
this._data = Encoding.UTF8.GetBytes(data);
break;
case DataType.UTF7:
this._data = Encoding.UTF7.GetBytes(data);
break;
case DataType.UTF32:
this._data = Encoding.UTF32.GetBytes(data);
break;
case DataType.ASCII:
this._data = Encoding.ASCII.GetBytes(data);
break;
case DataType.Unicode:
this._data = Encoding.Unicode.GetBytes(data);
break;
case DataType.Hex:
this._data = new byte[data.Length / 2];
for (int i = 0; i < data.Length; i += 2)
{
this._data[i / 2] = Convert.ToByte(data.Substring(i, 2), 16);
}
break;
case DataType.Base64:
this._data = Convert.FromBase64String(data);
break;
case DataType.Base32:
this._data = this.FromBase32String(data);
break;
}
}
public DataBlock(byte[] data)
{
this._data = data;
}
public string UTF8
{
get
{
return Encoding.UTF8.GetString(this._data);
}
}
public string UTF7
{
get
{
return Encoding.UTF7.GetString(this._data);
}
}
public string UTF32
{
get
{
return Encoding.UTF32.GetString(this._data);
}
}
public string ASCII
{
get
{
return Encoding.ASCII.GetString(this._data);
}
}
public string Unicode
{
get
{
return Encoding.Unicode.GetString(this._data);
}
}
public string Hex
{
get
{
return BitConverter.ToString(this._data).Replace("-", "");
}
}
public string Base64
{
get
{
return Convert.ToBase64String(this._data);
}
}
public string Base32
{
get
{
return this.ToBase32String(this._data);
}
}
public byte[] Bytes
{
get
{
return this._data;
}
}
private string ValidChars = "QAZ2WSX3" + "EDC4RFV5" + "TGB6YHN7" + "UJM8K9LP";
private string ToBase32String(byte[] bytes)
{
StringBuilder sb = new StringBuilder();
byte index;
int hi = 5;
int currentByte = 0;
while (currentByte < bytes.Length)
{
if (hi > 8)
{
index = (byte)(bytes[currentByte++] >> (hi - 5));
if (currentByte != bytes.Length)
{
index = (byte)(((byte)(bytes[currentByte] << (16 - hi)) >> 3) | index);
}
hi -= 3;
}
else if (hi == 8)
{
index = (byte)(bytes[currentByte++] >> 3);
hi -= 3;
}
else
{
index = (byte)((byte)(bytes[currentByte] << (8 - hi)) >> 3);
hi += 5;
}
sb.Append(ValidChars[index]);
}
return sb.ToString();
}
public byte[] FromBase32String(string str)
{
int numBytes = str.Length * 5 / 8;
byte[] bytes = new Byte[numBytes];
str = str.ToUpper();
int bit_buffer;
int currentCharIndex;
int bits_in_buffer;
if (str.Length < 3)
{
bytes[0] = (byte)(ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5);
return bytes;
}
bit_buffer = (ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5);
bits_in_buffer = 10;
currentCharIndex = 2;
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)bit_buffer;
bit_buffer >>= 8;
bits_in_buffer -= 8;
while (bits_in_buffer < 8 && currentCharIndex < str.Length)
{
bit_buffer |= ValidChars.IndexOf(str[currentCharIndex++]) << bits_in_buffer;
bits_in_buffer += 5;
}
}
return bytes;
}
}
Function to generate a keypair
public static DataBlock[] GenerateKeyPair(KeyPairSize keyPairSize)
{
RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), (int) keyPairSize));
AsymmetricCipherKeyPair keyPair = keyPairGenerator.GenerateKeyPair();
PrivateKeyInfo pkInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);
DataBlock[] keyPairData = new DataBlock[2];
keyPairData[0] = new DataBlock(pkInfo.GetDerEncoded());
keyPairData[1] = new DataBlock(info.GetDerEncoded());
return keyPairData;
}
Code to reproduce the error:
DataBlock[] keyPair = AsymmetricEncryption.GenerateKeyPair(AsymmetricEncryption.KeyPairSize.Bits512);
DataBlock pass = new DataBlock("1234", DataBlock.DataType.UTF8);
DataBlock orig = new DataBlock("Hello World", DataBlock.DataType.UTF8);
DataBlock encrypted = AsymmetricEncryption.Encrypt(orig, keyPair[1]);
AsymmetricEncryption.SaveKeyEncrypted(keyPair[0], "D:\\privateenc", pass);
AsymmetricEncryption.SaveKeyEncrypted(keyPair[1], "D:\\publicenc", pass);
DataBlock privateKey = AsymmetricEncryption.ReadKeyEncrypted("D:\\privateenc", pass);
DataBlock publicKey = AsymmetricEncryption.ReadKeyEncrypted("D:\\publicenc", pass);
DataBlock decrypted = AsymmetricEncryption.Decrypt(encrypted, privateKey);
Console.WriteLine(decrypted.UTF8);
The encryption/decryption method is not needed because the error already happens after reading the encrypted key on my harddrive.
Why/where is the extra data added and how can I fix it?
I was able to fix it by adding the initial byte array length of the key to the encrypted text and read it later on. In the read function I cut everything after the original size of the key.
The main problem is still present and this is just a workaround.
I'm trying to implement AES 128 CTR encryption in c#. I've found Bouncy Castle is very useful. Here is my code:
public class AESCrypto
{
private byte[] Key = new byte[16];
private byte[] IV = new byte[16];
private const int CHUNK_SIZE = 16;
private IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding");
// Key and IV I get from client.
public AESCrypto(byte[] key, byte[] iv, bool forEncryption) {
Key = key;
IV = iv;
cipher.Init(forEncryption, new ParametersWithIV(new KeyParameter(Key), IV));
}
public byte[] PerformAES(byte[] incomingBytes)
{
int blockCount = incomingBytes.Length / CHUNK_SIZE; // Number of blocks
int blockRemaining = incomingBytes.Length % CHUNK_SIZE; // Remaining bytes of the last block
byte[] outcomingBytes = new byte[incomingBytes.Length];
for (var i = 0; i < blockCount; i++)
{
// Why do I need to re-init it again?
//cipher.Init(false, new ParametersWithIV(new KeyParameter(Key), IV));
byte[] temp = new byte[CHUNK_SIZE];
Array.Copy(incomingBytes, i * CHUNK_SIZE, temp, 0, CHUNK_SIZE);
byte[] decryptedChunk = cipher.ProcessBytes(temp);
Array.Copy(decryptedChunk, 0, outcomingBytes, i * CHUNK_SIZE, CHUNK_SIZE);
//Increase(IV); Why do I need to increse iv by hand?
}
if (blockRemaining != 0)
{
// Why do I need to re-init it again?
//cipher.Init(false, new ParametersWithIV(new KeyParameter(Key), IV));
byte[] temp = new byte[blockRemaining];
Array.Copy(incomingBytes, incomingBytes.Length - blockRemaining, temp, 0, blockRemaining);
byte[] decryptedChunk = cipher.DoFinal(temp);
Array.Copy(decryptedChunk, 0, outcomingBytes, incomingBytes.Length - blockRemaining, blockRemaining);
//Increase(IV); Why do I need to increse iv by hand?
}
return outcomingBytes;
}
private void Increase(byte[] iv)
{
for (var i = 0; i < iv.Length; i++)
{
iv[i]++;
if (iv[i] != 0)
{
break;
}
}
}
}
At first glance, this code should work fine. But it does not. Pay attention to commented-out-lines:
//cipher.Init(false, new ParametersWithIV(new KeyParameter(Key), IV));
and
//Increase(IV); Why do I need to increase iv by hand?
Only if I uncomment them my code works fine.
I'm wondering why I have to increase the counter manually? Or I made a mistake somewhere in set-up in the constructor? I'm not very familiar with Bouncy Castle.
P.S. I'm using BC 1.8.6.1 version from Nuget.
Long story short have a membership system built in .NET that we are porting to WordPress and need to replicate the PBKDF2 encryption so users don't need to reset their passwords.
Using a know hashed password I've been able to replicate this in .NET easily, with the following code:
static void Main(string[] args)
{
var isValid = CheckPassword("#0zEZcD7uNmv", "5SyOX+Rbclzvvit3MEM2nBRaPVo2M7ZTs7n3znXTfyW4OhwTlJLvpcUlCryblgkQ");
}
public static int PBKDF2IterCount = 10000;
public static int PBKDF2SubkeyLength = 256 / 8; // 256 bits
public static int SaltSize = 128 / 8; // 128 bits
private static bool CheckPassword(string Password, string ExistingHashedPassword)
{
byte[] saltAndPassword = Convert.FromBase64String(ExistingHashedPassword);
byte[] salt = new byte[SaltSize];
Array.Copy(saltAndPassword, 0, salt, 0, SaltSize);
Console.WriteLine("--Salt--");
Console.WriteLine(Convert.ToBase64String(salt));
string hashedPassword = HashPassword(Password, salt);
Console.WriteLine("--HashedPassword--");
Console.WriteLine(hashedPassword);
return hashedPassword == ExistingHashedPassword;
}
private static string HashPassword(string Password, byte[] salt)
{
byte[] hash = new byte[PBKDF2SubkeyLength];
using (var pbkdf2 = new Rfc2898DeriveBytes(Password, salt, PBKDF2IterCount))
{
hash = pbkdf2.GetBytes(PBKDF2SubkeyLength);
}
byte[] hashBytes = new byte[PBKDF2SubkeyLength + SaltSize];
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
Array.Copy(hash, 0, hashBytes, SaltSize, PBKDF2SubkeyLength);
string hashedPassword = Convert.ToBase64String(hashBytes);
return hashedPassword;
}
The console app will produce the following:
--Salt--
5SyOX+Rbclzvvit3MEM2nA==
--HashedPassword--
5SyOX+Rbclzvvit3MEM2nBRaPVo2M7ZTs7n3znXTfyW4OhwTlJLvpcUlCryblgkQ
--IsValid--
True
However in the PHP side I can't get the same results. My code so far is below.
$mySalt = base64_decode('5SyOX+Rbclzvvit3MEM2nA==');
$dev = pbkdf2('sha1', '#0zEZcD7uNmv', $mySalt, 10000, 48, true);
$key = substr($dev, 0, 32); //Keylength: 32
$iv = substr($dev, 32, 16); // IV-length: 16
echo 'PHP<br/>';
echo 'PASS: '.base64_encode($dev).'<br/>';
echo 'SALT: '.base64_encode($iv).'<br/><br/>';
echo '.NET<br/>';
echo 'PASS: 5SyOX+Rbclzvvit3MEM2nBRaPVo2M7ZTs7n3znXTfyW4OhwTlJLvpcUlCryblgkQ<br/>';
echo 'SALT: 5SyOX+Rbclzvvit3MEM2nA==<br/><br/>';
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
$algorithm = strtolower($algorithm);
if(!in_array($algorithm, hash_algos(), true))
die('PBKDF2 ERROR: Invalid hash algorithm.');
if($count <= 0 || $key_length <= 0)
die('PBKDF2 ERROR: Invalid parameters.');
$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);
$output = "";
for($i = 1; $i <= $block_count; $i++) {
// $i encoded as 4 bytes, big endian.
$last = $salt . pack("N", $i);
// first iteration
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
// perform the other $count - 1 iterations
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
return substr($output, 0, $key_length);
}
And the results are:
PHP
PASS: FFo9WjYztlOzuffOddN/Jbg6HBOUku+lxSUKvJuWCRCsYe+1Tgbb8Ob4FtxumMal
SALT: rGHvtU4G2/Dm+BbcbpjGpQ==
.NET
PASS: 5SyOX+Rbclzvvit3MEM2nBRaPVo2M7ZTs7n3znXTfyW4OhwTlJLvpcUlCryblgkQ
SALT: 5SyOX+Rbclzvvit3MEM2nA==
Any help would be appreciated.
Ended up getting it working using the https://github.com/defuse/password-hashing libraries, with some minor changes match the format of hashes I was working with database I'm importing.
But my main problem was with these lines where I'm trying to get a key out of a hash.
$dev = pbkdf2('sha1', '#0zEZcD7uNmv', $mySalt, 10000, 48, true);
$key = substr($dev, 0, 32); //Keylength: 32
$iv = substr($dev, 32, 16); // IV-length: 16
Changing it to the below, so that it is creating a hash hash that is 32 bits long and joining the returning hash to the salt fixed the issue.
$dev = pbkdf2('sha1', '#0zEZcD7uNmv', $mySalt, 10000, 32, true);
echo 'PASS: '.base64_encode($mySalt.$dev).'<br />';
With the output below now matching .NET:
PASS: 5SyOX+Rbclzvvit3MEM2nBRaPVo2M7ZTs7n3znXTfyW4OhwTlJLvpcUlCryblgkQ
I ran into this post while searching for a way to migrate passwords from a legacy Asp.Net MVC application to Laravel.
For those interested in just comparing the generated hash (ie. for authentication purpose), please consider the following:
function legacyHashCheck($hash, $password)
{
$raw = base64_decode($hash);
$salt = substr($raw, 1, 16);
$payload = substr($raw, 17, 32);
//new Rfc2898DeriveBytes(password, salt, 1000).GetBytes(32)
$check = hash_pbkdf2('sha1', $password, $salt, 1000, 32, true);
return $payload === $check;
}
It seems .NET core implements 2 formats now (2022).
Source
https://github.com/dotnet/AspNetCore/blob/main/src/Identity/Extensions.Core/src/PasswordHasher.cs
I needed to implement both for Laravel, so here is my contribution:
private function dotNetVerifyHash($hash, $password) {
$version = ord($hash[0]);
if ($version !== 0 && $version !== 1) {
throw new \Exception('wrong version header: ' . $version);
}
if ($version === 0) {
// Format: { 0x00, salt, subkey }
$iterations = 1000;
$subKeyLength = 32;
$saltSize = 16;
$salt = substr($hash, 1, $saltSize);
$derived = hash_pbkdf2('sha1', $password, $salt, $iterations, $subKeyLength, true);
$newHash = chr(0x00) . $salt . $derived;
} else if ($version === 1) {
// Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
$unp = unpack('N3', substr($hash, 1, 12));
$prf = $unp[1];
$algorithm = '';
switch ($prf) {
case 0: $algorithm = 'sha1'; break;
case 1: $algorithm = 'sha256'; break;
case 2: $algorithm = 'sha512'; break;
default: throw new \Exception('invalid prf: ' . $prf);
}
$iterations = $unp[2];
$saltLength = $unp[3];
$subKeyLength = 32;
$salt = substr($hash, 13, $saltLength);
$derived = hash_pbkdf2($algorithm, $password, $salt, $iterations, $subKeyLength, true);
$newHash = chr(0x01) . pack('N3', $prf, $iterations, $saltLength) . $salt . $derived;
}
return $hash === $newHash;
}
function dotNetCreateHash($password, $version = 1) {
if ($version !== 0 && $version !== 1) {
throw new \Exception('invalid version: ' . ord($hash[0]));
}
$salt = Str::random(16);
if ($version === 0) {
// Format: { 0x00, salt, subkey }
$dev = hash_pbkdf2('sha1', $password, $salt, 1000, 32, true);
return base64_encode(chr(0x00) . $salt . $dev);
} else if ($version === 1) {
// Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
$algorithm = 'sha256';
$prf = 1;
$iterations = 10000;
$saltLength = strlen($salt);
$subKeyLength = 32;
$derived = hash_pbkdf2($algorithm, $password, $salt, $iterations, $subKeyLength, true);
return base64_encode(chr(0x01) . pack('N3', $prf, $iterations, $saltLength) . $salt . $derived);
}
}
And you can also extend Laravel with custom hasher:
https://gist.github.com/tonila/5719aea8ad57df6821d7acdd1ed4ef1a