Related
I trying to add certificate extension to my X509Certificate2 object in pure .NET 4.7.2
I was using BouncyCastle by this method:
private static void AddCdpUrl(X509V3CertificateGenerator certificateGenerator, string cdptUrl)
{
var uriGeneralName = new GeneralName(GeneralName.UniformResourceIdentifier, cdptUrl);
var cdpName = new DistributionPointName(DistributionPointName.FullName, uriGeneralName);
var cdp = new DistributionPoint(cdpName, null, null);
certificateGenerator.AddExtension(X509Extensions.CrlDistributionPoints, false, new CrlDistPoint(new[] { cdp }));
}
Add its works and I get great result:
Now in pure .NET I am using this method:
const string X509CRLDistributionPoints = "2.5.29.31";
certificateRequest.CertificateExtensions.Add(new X509Extension(new Oid(X509CRLDistributionPoints), Encoding.UTF8.GetBytes("http://crl.example.com"), false));
And get this result:
I am missing the sequences for "Distribution Point Name", "Full Name" and "URL="
How can I generate the same result that BouncyCastle does with pure .NET
Thanks
If you only want to write one distribution point, and it's less than or equal to 119 ASCII characters long, and you aren't delegating CRL signing authority to a different certificate:
private static X509Extension MakeCdp(string url)
{
byte[] encodedUrl = Encoding.ASCII.GetBytes(url);
if (encodedUrl.Length > 119)
{
throw new NotSupportedException();
}
byte[] payload = new byte[encodedUrl.Length + 10];
int offset = 0;
payload[offset++] = 0x30;
payload[offset++] = (byte)(encodedUrl.Length + 8);
payload[offset++] = 0x30;
payload[offset++] = (byte)(encodedUrl.Length + 6);
payload[offset++] = 0xA0;
payload[offset++] = (byte)(encodedUrl.Length + 4);
payload[offset++] = 0xA0;
payload[offset++] = (byte)(encodedUrl.Length + 2);
payload[offset++] = 0x86;
payload[offset++] = (byte)(encodedUrl.Length);
Buffer.BlockCopy(encodedUrl, 0, payload, offset, encodedUrl.Length);
return new X509Extension("2.5.29.31", payload, critical: false);
}
Past 119 characters the outer payload length exceeds 0x7F and then you really start wanting a proper DER encoder. You definitely want one for variable numbers of URLs, or including any of the optional data from the extension.
This is probably a bit late, but you could use BC as a utility to get the DER-encoded extension and import it into native .NET like so:
// req is a .NET Core 3.1 CertificateRequest object
req.CertificateExtensions.Add(
new X509Extension(
new Oid("2.5.29.31"),
crlDistPoint.GetDerEncoded(), // this is your CRL extension
false
)
);
I ran into this problem and now there is a AsnWriter available (thanks #bartonjs, looks like you got the namespace you wanted :)), if you're on at least NET5.
So I've cobbled together a method which creates the extension using the writer:
/// <summary>Derived from https://github.com/dotnet/runtime/blob/main/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/Asn1/DistributionPointAsn.xml.cs</summary>
private static X509Extension BuildDistributionPointExtension(string[] fullNames, ReasonFlagsAsn? reasons, string[]? crlIssuers, bool critical) {
var writer = new AsnWriter(AsnEncodingRules.DER);
writer.PushSequence();
writer.PushSequence(Asn1Tag.Sequence);
writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0));
writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0));
//See https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Security/Cryptography/Asn1/GeneralNameAsn.xml.cs for different value types
for(int i = 0; i < fullNames.Length; i++) writer.WriteCharacterString(UniversalTagNumber.IA5String, fullNames[i], new Asn1Tag(TagClass.ContextSpecific, 6)); //GeneralName 6=URI
writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, 0));
writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, 0));
if(reasons.HasValue) writer.WriteNamedBitList(reasons.Value, new Asn1Tag(TagClass.ContextSpecific, 1));
if(crlIssuers?.Length > 0) {
writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 2));
for(int i = 0; i < crlIssuers.Length; i++) writer.WriteCharacterString(UniversalTagNumber.IA5String, crlIssuers[i], new Asn1Tag(TagClass.ContextSpecific, 2)); //GeneralName 2=DnsName
writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, 2));
}
writer.PopSequence(Asn1Tag.Sequence);
writer.PopSequence();
return new X509Extension(new Oid("2.5.29.31"), writer.Encode(), critical);
}
[Flags] internal enum ReasonFlagsAsn { Unused = 1 << 0, KeyCompromise = 1 << 1, CACompromise = 1 << 2, AffiliationChanged = 1 << 3, Superseded = 1 << 4, CessationOfOperation = 1 << 5, CertificateHold = 1 << 6, PrivilegeWithdrawn = 1 << 7, AACompromise = 1 << 8 }
Just be careful and do your own tests as I've only checked if the CRL is properly displayed in a cert viewer.
I collect some large log infos using a C# tool. Therefore I searched for a way to compress that giant string and I found this snippet to do the trick:
public static string CompressString(string text)
{
byte[] buffer = Encoding.UTF8.GetBytes(text);
var memoryStream = new MemoryStream();
using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
{
gZipStream.Write(buffer, 0, buffer.Length);
}
memoryStream.Position = 0;
var compressedData = new byte[memoryStream.Length];
memoryStream.Read(compressedData, 0, compressedData.Length);
var gZipBuffer = new byte[compressedData.Length + 4];
Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length);
Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4);
return Convert.ToBase64String(gZipBuffer);
}
After my logging action the C# tool sends this compressed String to a node.js REST interface which writes it into a database.
Now (in my naive understanding of compression) I thought that I could simply use something like the follwoing code on nodejs side to uncompress it:
zlib.gunzip(Buffer.from(compressedLogMessage, 'base64'), function(err, uncompressedLogMessage) {
if(err) {
console.error(err);
}
else {
console.log(uncompressedLogMessage.toString('utf-8'));
}
});
But I get the error:
{ Error: incorrect header check
at Zlib._handle.onerror (zlib.js:370:17) errno: -3, code: 'Z_DATA_ERROR' }
It seems that the compression method does not match with the uncompression function. I expect that anyone with compression/uncompression knowledge could maybe see the issue(s) immediately.
What could I change or improve to make the uncompression work?
Thanks a lot!
========== UPDATE ===========
It seems that message receiving and base64 decoding works..
Using CompressString("Hello World") results in:
// before compression
"Hello World"
// after compression before base64 encoding
new byte[] { 11, 0, 0, 0, 31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 243, 72, 205, 201, 201, 87, 8, 207, 47, 202, 73, 1, 0, 86, 177, 23, 74, 11, 0, 0, 0 }
// after base64 encoding
CwAAAB+LCAAAAAAAAAPzSM3JyVcIzy/KSQEAVrEXSgsAAAA=
And on node js side:
// after var buf = Buffer.from('CwAAAB+LCAAAAAAAAAPzSM3JyVcIzy/KSQEAVrEXSgsAAAA=', 'base64');
{"buf":{"type":"Buffer","data":[11,0,0,0,31,139,8,0,0,0,0,0,0,3,243,72,205,201,201,87,8,207,47,202,73,1,0,86,177,23,74,11,0,0,0]}}
// after zlib.gunzip(buf, function(err, dezipped) { ... }
{ Error: incorrect header check
at Zlib._handle.onerror (zlib.js:370:17) errno: -3, code: 'Z_DATA_ERROR' }
=============== Update 2 ==================
#01binary's answer was correct! That's the working solution:
function toArrayBuffer(buffer) {
var arrayBuffer = new ArrayBuffer(buffer.length);
var view = new Uint8Array(arrayBuffer);
for (var i = 0; i < buffer.length; ++i) {
view[i] = buffer[i];
}
return arrayBuffer;
}
// Hello World (compressed with C#) => CwAAAB+LCAAAAAAAAAPzSM3JyVcIzy/KSQEAVrEXSgsAAAA=
var arrayBuffer = toArrayBuffer(Buffer.from('CwAAAB+LCAAAAAAAAAPzSM3JyVcIzy/KSQEAVrEXSgsAAAA=', 'base64'))
var zlib = require('zlib');
zlib.gunzip(Buffer.from(arrayBuffer, 4), function(err, uncompressedMessage) {
if(err) {
console.log(err)
}
else {
console.log(uncompressedMessage.toString()) // Hello World
}
});
The snippet you found appears to write 4 extra bytes to the beginning of the output stream, containing the "uncompressed" size of the original data. The original author must have assumed that logic on the receiving end is going to read those 4 bytes, know that it needs to allocate a buffer of that size, and pass the rest of the stream (at +4 offset) to gunzip.
If you are using this signature on the Node side:
https://nodejs.org/api/buffer.html#buffer_class_method_buffer_from_arraybuffer_byteoffset_length
...then pass a byte offset of 4. The first two bytes of your gzip stream should be { 0x1F, 0x8b }, and you can see in your array that those two bytes start at offset 4. A simple example of the zlib header can be found here:
Zlib compression incompatibile C vs C# implementations
I am trying to get same result using TripleDES using C++ app which has Crypto++ and .NET app which uses TripleDESCryptoServiceProvider. I tried setting Key and IV the same but I am getting different results.
This question was already asked here, but there is no clear answer.
Here is C++ example
#include <stdio.h>
#include <cstdlib>
#include <string>
#include <iostream>
#include "dll.h"
#include "mybase64.h"
using namespace std;
USING_NAMESPACE(CryptoPP)
int main()
{
std::cout << "Crypto++ Example" << endl;
std:cout << "TEST" << endl;
const int textSize = 4;
const int keySize = 24;
byte iv[] = { 240, 4, 37, 12, 167, 153, 233, 177 };
byte key[] = {191, 231, 220, 196, 173, 36, 92, 125, 146, 210, 117, 220, 95, 104, 154, 69, 180, 113, 146, 19, 124, 62, 60, 79};
byte encryptedText[textSize];
char cText[] = {'T', 'E', 'S', 'T'};
byte* text = new byte[textSize];
for (int ndx = 0; ndx<4; ndx++)
{
text[ndx] = (byte)cText[ndx];
}
CFB_FIPS_Mode<DES_EDE3>::Encryption encryption;
encryption.SetKeyWithIV(key, keySize, iv);
encryption.ProcessString(encryptedText, text, 4);
string encoded;
encoded = base64_encode(encryptedText, 4);
cout << encoded << endl;
system("pause");
return 0;
}
which produces following result:
K3zUUA==
Here is C# example:
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace TripleDESExample
{
class Program
{
static void Main(string[] args)
{
string message = "TEST";
byte[] iv = { 240, 4, 37, 12, 167, 153, 233, 177 };
byte[] key = { 191, 231, 220, 196, 173, 36, 92, 125, 146, 210, 117, 220, 95, 104, 154, 69, 180, 113, 146, 19, 124, 62, 60, 79 };
byte[] data = Encoding.ASCII.GetBytes(message);
using (var tdes = new TripleDESCryptoServiceProvider())
{
tdes.Mode = CipherMode.CFB;
tdes.Padding = PaddingMode.Zeros;
tdes.IV = iv;
tdes.Key = key;
using (var ms = new MemoryStream())
{
using (var crypto = new CryptoStream(ms, tdes.CreateEncryptor(), CryptoStreamMode.Write))
{
crypto.Write(data, 0, data.Length);
crypto.Close();
}
Array.Copy(ms.ToArray(), data, data.Length);
Console.WriteLine(string.Format("Encrypted: {0}", Convert.ToBase64String(data)));
}
}
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
}
Which produces following result:
K7nXyg==
So you can see that they produce different result.
K7nXyg==
K3zUUA==
Can anyone point what could be the issue for them showing different result.
If possible please provide example code.
---------------------UPDATE 4/27/2017-----------------------------------------
Now tried using a little differently implementation of .NET giving me different result as well...
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace TripleDESExample
{
class Program
{
static void Main(string[] args)
{
string message = "TEST";
byte[] iv = { 240, 4, 37, 12, 167, 153, 233, 177 };
byte[] key = { 191, 231, 220, 196, 173, 36, 92, 125, 146, 210, 117, 220, 95, 104, 154, 69, 180, 113, 146, 19, 124, 62, 60, 79 };
byte[] bytes = Encoding.ASCII.GetBytes(message);
TripleDESCryptoServiceProvider cryptoServiceProvider1 = new TripleDESCryptoServiceProvider();
cryptoServiceProvider1.Key = key;
cryptoServiceProvider1.IV = iv;
cryptoServiceProvider1.Mode = CipherMode.CFB;
cryptoServiceProvider1.Padding = PaddingMode.Zeros;
TripleDESCryptoServiceProvider cryptoServiceProvider2 = cryptoServiceProvider1;
byte[] inArray = cryptoServiceProvider2.CreateEncryptor().TransformFinalBlock(bytes, 0, bytes.Length);
cryptoServiceProvider2.Clear();
Console.WriteLine(string.Format("Encrypted: {0}", Convert.ToBase64String(inArray, 0, inArray.Length)));
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
}
which gives me:
K7nXyp+x9kY=
Why?
------UPDATE 4/28/2017-----------
This article describes very well Crypto++ implementation.
When I try to increment BlockSize and FeedbackSize I get following error:
Based on the discussion here it seems like .NET TripleDESCryptoServiceProvider uses CipherMode.CFB in 8-bit while Crypto++ uses it with 128-bit. When trying to set FeedbackSize for .NET higher it throws exception.
Does anyone know how to resolve this issue?
From the comments:
The issue is likely the feedback size. I believe .Net uses a small feedback size, like 8-bits, for CFB mode. Crypto++ uses the full block size for CFB mode. I'd recommend getting a baseline using CBC mode. Once you arrive at the same result in .Net and Crypto++, then switch to CFB mode and turn knobs on the feedback size.
Do you have example how to accomplish this?
You can find examples of CBC Mode on the Crypto++ wiki. Other wiki pages of interest may be TripleDES and CFB Mode.
You can also find test vectors for these modes of operation on the NIST website.
You really need to get to a baseline. You should not use random messages and random keys and ivs until you achieve your baseline.
Here's an example of using a less-than-blocksize feedback size in Crypto++. The example is available at CFB Mode on the Crypto++ wiki (we added it for this answer). You will have to dial in your parameters random parameters (but I suggest you baseline first with something like the NIST test vectors).
You should be wary of using a feedback size that is smaller than the block size because it can reduce the security of the block cipher. If given a choice, you should increase the feedback size for Mcrypt or .Net; and not reduce the feedback size for Crypto++.
SecByteBlock key(AES::DEFAULT_KEYLENGTH), iv(AES::BLOCKSIZE);
memset(key, 0x00, key.size());
memset(iv, 0x00, iv.size());
AlgorithmParameters params = MakeParameters(Name::FeedbackSize(), 1 /*8-bits*/)
(Name::IV(), ConstByteArrayParameter(iv));
string plain = "CFB Mode Test";
string cipher, encoded, recovered;
/*********************************\
\*********************************/
try
{
cout << "plain text: " << plain << endl;
CFB_Mode< AES >::Encryption enc;
enc.SetKey( key, key.size(), params );
StringSource ss1( plain, true,
new StreamTransformationFilter( enc,
new StringSink( cipher )
) // StreamTransformationFilter
); // StringSource
}
catch( CryptoPP::Exception& ex )
{
cerr << ex.what() << endl;
exit(1);
}
/*********************************\
\*********************************/
// Pretty print cipher text
StringSource ss2( cipher, true,
new HexEncoder(
new StringSink( encoded )
) // HexEncoder
); // StringSource
cout << "cipher text: " << encoded << endl;
/*********************************\
\*********************************/
try
{
CFB_Mode< AES >::Decryption dec;
dec.SetKey( key, key.size(), params );
// The StreamTransformationFilter removes
// padding as required.
StringSource ss3( cipher, true,
new StreamTransformationFilter( dec,
new StringSink( recovered )
) // StreamTransformationFilter
); // StringSource
cout << "recovered text: " << recovered << endl;
}
catch( CryptoPP::Exception& ex )
{
cerr << ex.what() << endl;
exit(1);
}
It produces the following output:
$ ./test.exe
plain text: CFB Mode Test
cipher text: 2506FBCA6F97DC7653B414C291
recovered text: CFB Mode Test
So you can see that they produce different result.
K7nXyg==
K3zUUA==
The following reproduces K7nXyg==, but its not clear to me that's what you want. You really should get to your baseline. Then you can tell us things like a key with no parity and an 8-bit feedback size.
const byte key[] = { 191, 231, 220, 196, 173, 36, 92, 125,
146, 210, 117, 220, 95, 104, 154, 69,
180, 113, 146, 19, 124, 62, 60, 79 };
const byte iv[] = { 240, 4, 37, 12, 167, 153, 233, 177 };
ConstByteArrayParameter cb(iv, sizeof(iv));
AlgorithmParameters params = MakeParameters(Name::FeedbackSize(), 1 /*8-bits*/)
(Name::IV(), ConstByteArrayParameter(iv, sizeof(iv)));
string plain = "TEST";
string cipher, encoded, recovered;
/*********************************\
\*********************************/
try
{
cout << "plain text: " << plain << endl;
CFB_Mode< DES_EDE3 >::Encryption enc;
enc.SetKey( key, sizeof(key), params );
StringSource ss1( plain, true,
new StreamTransformationFilter( enc,
new StringSink( cipher )
) // StreamTransformationFilter
); // StringSource
}
catch( CryptoPP::Exception& ex )
{
cerr << ex.what() << endl;
exit(1);
}
/*********************************\
\*********************************/
// Pretty print cipher text
StringSource ss2( cipher, true,
new Base64Encoder(
new StringSink( encoded )
) // HexEncoder
); // StringSource
cout << "cipher text: " << encoded << endl;
/*********************************\
\*********************************/
try
{
CFB_Mode< DES_EDE3 >::Decryption dec;
dec.SetKey( key, sizeof(key), params );
// The StreamTransformationFilter removes
// padding as required.
StringSource ss3( cipher, true,
new StreamTransformationFilter( dec,
new StringSink( recovered )
) // StreamTransformationFilter
); // StringSource
cout << "recovered text: " << recovered << endl;
}
catch( CryptoPP::Exception& ex )
{
cerr << ex.what() << endl;
exit(1);
}
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
i try to decoding G711 packet i find this code in c++ but i do not know how to convert this code to c#
this the code
gint16 alaw_exp_table[256] = {
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
-7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
-2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
-3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
-22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
-30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
-11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472,
-15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
-344, -328, -376, -360, -280, -264, -312, -296,
-472, -456, -504, -488, -408, -392, -440, -424,
-88, -72, -120, -104, -24, -8, -56, -40,
-216, -200, -248, -232, -152, -136, -184, -168,
-1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
-1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
-688, -656, -752, -720, -560, -528, -624, -592,
-944, -912, -1008, -976, -816, -784, -880, -848,
5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
344, 328, 376, 360, 280, 264, 312, 296,
472, 456, 504, 488, 408, 392, 440, 424,
88, 72, 120, 104, 24, 8, 56, 40,
216, 200, 248, 232, 152, 136, 184, 168,
1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
688, 656, 752, 720, 560, 528, 624, 592,
944, 912, 1008, 976, 816, 784, 880, 848};
#include <glib.h>
#include "G711adecode.h"
#include "G711atable.h"
int
decodeG711a(void *input, int inputSizeBytes, void *output, int *outputSizeBytes)
{
guint8 *dataIn = (guint8 *)input;
gint16 *dataOut = (gint16 *)output;
int i;
for (i=0; i<inputSizeBytes; i++)
{
dataOut[i] = alaw_exp_table[dataIn[i]];
}
*outputSizeBytes = inputSizeBytes * 2;
return 0;
}
can any body help me ?
thanks in advance
This is a short[].
You can translate it like any other array:
short[] x = { 1,2,3 };
Looks like this is a fairly simple lossy encoding of a short array. Since G711 is an audio codec, that makes sense. Just iterate over the input byte array, and put the values from the lookup table in the output short array.
private short[] alaw_exp_table = {/* same data as the C code */};
public short[] decodeG711a(byte[] input)
{
short[] result = new short[input.Length];
for (int i = 0; i < input.Length; i++)
{
result[i] = alaw_exp_table[input[i]];
}
return result;
}