CryptDeriveKey algorithm names - c#

public byte[] CryptDeriveKey(
string algname,
string alghashname,
int keySize,
byte[] rgbIV
)
Can someone please enlighten me as to what options there are in algname? If I want to specify an encryption algorithm for AES-128 and AES-256, what should I put in the algname?

I'm not 100% sure but, algname is your algorithm name. keySize is size of the key.
You should use AES-128 and AES-256 like this;
CryptDeriveKey("AES", "SHA1", 128, aes.IV)
and
CryptDeriveKey("AES", "SHA1", 256, aes.IV)
Check out for more details from MSDN.
Here is a decompiled code for PasswordDeriveBytes.CryptDeriveKey method.
[SecuritySafeCritical]
public byte[] CryptDeriveKey(string algname, string alghashname, int keySize, byte[] rgbIV)
{
if (keySize < 0)
{
throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidKeySize"));
}
int algidHash = X509Utils.NameOrOidToAlgId(alghashname, OidGroup.HashAlgorithm);
if (algidHash == 0)
{
throw new CryptographicException(Environment.GetResourceString("Cryptography_PasswordDerivedBytes_InvalidAlgorithm"));
}
int algid = X509Utils.NameOrOidToAlgId(algname, OidGroup.AllGroups);
if (algid == 0)
{
throw new CryptographicException(Environment.GetResourceString("Cryptography_PasswordDerivedBytes_InvalidAlgorithm"));
}
if (rgbIV == null)
{
throw new CryptographicException(Environment.GetResourceString("Cryptography_PasswordDerivedBytes_InvalidIV"));
}
byte[] o = null;
DeriveKey(this.ProvHandle, algid, algidHash, this._password, this._password.Length, keySize << 0x10, rgbIV, rgbIV.Length, JitHelpers.GetObjectHandleOnStack<byte[]>(ref o));
return o;
}
Here is a decompiled code of NameOrOidToAlgId method.
internal static int NameOrOidToAlgId(string oid, OidGroup oidGroup)
{
if (oid == null)
{
return 0x8004;
}
string str = CryptoConfig.MapNameToOID(oid, oidGroup);
if (str == null)
{
str = oid;
}
int algIdFromOid = GetAlgIdFromOid(str, oidGroup);
switch (algIdFromOid)
{
case 0:
case -1:
throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidOID"));
}
return algIdFromOid;
}

I don't have here a dissasembler like Reflector to find the solution. If you have one go to mscorlib.dll and decompile PasswordDerivedBytes.CryptDeriveKey method. You will find there the supported strings.
By the way, AES is not supported: here

One can use TripleDES for algname parameter.
Ex:
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes("password", 24, 1000);
byte[] key = pdb.CryptDeriveKey("TripleDES", "SHA256", 192, tdes.IV);
Click here to see documentation here

Related

PHP RSA public encrypt not matching C# encrypt

I Am trying to replicate the RSA public key encryption a C# project is using with PHP
I am using this code using C# and a public key.
public static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509key)
{
byte[] SeqOID = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
MemoryStream ms = new MemoryStream(x509key);
BinaryReader reader = new BinaryReader(ms);
if (reader.ReadByte() == 0x30)
ReadASNLength(reader); //skip the size
else
return null;
int identifierSize = 0; //total length of Object Identifier section
if (reader.ReadByte() == 0x30)
identifierSize = ReadASNLength(reader);
else
return null;
if (reader.ReadByte() == 0x06) //is the next element an object identifier?
{
int oidLength = ReadASNLength(reader);
byte[] oidBytes = new byte[oidLength];
reader.Read(oidBytes, 0, oidBytes.Length);
if (oidBytes.SequenceEqual(SeqOID) == false) //is the object identifier rsaEncryption PKCS#1?
return null;
int remainingBytes = identifierSize - 2 - oidBytes.Length;
reader.ReadBytes(remainingBytes);
}
if (reader.ReadByte() == 0x03) //is the next element a bit string?
{
ReadASNLength(reader); //skip the size
reader.ReadByte(); //skip unused bits indicator
if (reader.ReadByte() == 0x30)
{
ReadASNLength(reader); //skip the size
if (reader.ReadByte() == 0x02) //is it an integer?
{
int modulusSize = ReadASNLength(reader);
byte[] modulus = new byte[modulusSize];
reader.Read(modulus, 0, modulus.Length);
if (modulus[0] == 0x00) //strip off the first byte if it's 0
{
byte[] tempModulus = new byte[modulus.Length - 1];
Array.Copy(modulus, 1, tempModulus, 0, modulus.Length - 1);
modulus = tempModulus;
}
if (reader.ReadByte() == 0x02) //is it an integer?
{
int exponentSize = ReadASNLength(reader);
byte[] exponent = new byte[exponentSize];
reader.Read(exponent, 0, exponent.Length);
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = new RSAParameters();
RSAKeyInfo.Modulus = modulus;
RSAKeyInfo.Exponent = exponent;
RSA.ImportParameters(RSAKeyInfo);
return RSA;
}
}
}
}
return null;
}
public static int ReadASNLength(BinaryReader reader)
{
//Note: this method only reads lengths up to 4 bytes long as
//this is satisfactory for the majority of situations.
int length = reader.ReadByte();
if ((length & 0x00000080) == 0x00000080) //is the length greater than 1 byte
{
int count = length & 0x0000000f;
byte[] lengthBytes = new byte[4];
reader.Read(lengthBytes, 4 - count, count);
Array.Reverse(lengthBytes); //
length = BitConverter.ToInt32(lengthBytes, 0);
}
return length;
}
When using the same certificate and open_ssl with PHP I always get different results. I know that the c# solution is working, since it is capable to connect with the service (mc server) that my PHP script fails to connect to.
I narrowed it down to the php RSA encryption not giving the right output.
This is the php code I am currently using for the encryption:
$key = openssl_pkey_get_public( $cert);
openssl_public_encrypt("asdf",$result,$key, OPENSSL_PKCS1_PADDING);
echo base64_encode($result);
And here is an example of a public key (PEM):
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+1KNonOhZVYIKYhDyYh1rEGEMSq6gXT8PIpWs1AmEHowW6upSBMmwCvuwCCiA+rdcJue7FyLYDx7qvKAomDKzX4lKK7T5M7LU2el34nCYzMmiidp6cZ9IxIqvCNpJMIlPXuCXpVg1RtZxyWU7p0/PMQWSANy9JkDsGUuoGqnBIwIDAQAB
-----END PUBLIC KEY-----
I am trying to encrypt "asdf" with this key.
The c# code (which should be right since its being accepted by the server) is giving me this as a result
var test = RSAService.Encrypt(Encoding.UTF8.GetBytes("asdf"), false);
ConsoleIO.WriteLine(Convert.ToBase64String(dataTypes.GetArray(test)));
gAEqOSvVyTcqFxPPYB84EOE9nm4NFfu3rWihZStgAMlE4pPkT0ugeYWtiswSxSx48zlfqoaMh15nes0osRs4yGwhjv+FqqV12/FccpdWCMKp1WVluhDvOUmgI7v8a/YGOGCk6tyuPGcWiv0XawcFdvqwkCEBTc2Z2FI9nkAS9M6swg==
while PHP (using the code I posted above) is giving me
UpRBZX8mGr9NhY7l7VZYjFe3jFwJjEnGfx750cBEI4G0E+c6guKMlPLk9IM82nqjj9H62ohd2nN5Rs8iay1rchLLaVxSOdPnqaxb7t6VJ6jiwpwSTvfODd2Cafw3bzAEbCNIcl6t8LlekdYyx3onEIBLbWGGe/clxJMDqAXlr84=
Every help is appreciated, thank you very much!

PBKDF2 function with 128 Bit Key Length and 1,024 iterations of SHA256 in .Net Framework 4.5

I am trying to work with an api in .Net framework 4.5 that supposed to provide me cryptocurrencies wallet. in a part of it's documentations it says:
Pass Pin Code through the PBKDF2 function with 128 Bit Key Length and
1,024 iterations of SHA256
i could not find the Specify method in C# to do that. in documentations they have input "be9d3a4f1220495a96c38d36d8558365" as pin code and the out put is "4369cb0560d54f55d0c03564fbd983c4".
it seems that i should use Rfc2898DeriveBytes Method, and i used it like code below but i didnot get the same result.
string output = Convert.ToBase64String((new Rfc2898DeriveBytes("e24546d6643137a310968566cf1cd42b",16, 1024)).GetBytes(32));
output ==> 'x10zclBJY2eeZqjMyPfQm4ljyMFPvWbxF72Om2DCzHE='
It's probably best to implement your own version of PBKDF2. PBKDF2 is the actual algorithm implemented by the badly named Rfc2898DeriveBytes class.
As .NET 4.5 doesn't include the functionality to use PBKDF2 with a different hash. .NET version 4.7.2 does include the functionality but it doesn't allow the salt to be zero bytes.
So therefore it is best to implement your own version. The .NET version of Microsoft has specific copyright notices that do not seem compatible. One way to go around this is to implement PBKDF2 from Mono, but the later versions of Mono do not implement this class (it seems) and they do not implement the version where the hash can be chosen.
Fortunately bartonjs has indicated a version that has the permissive MIT license, which can be used, leading to the following solution:
using System;
using System.Security.Cryptography;
using System.Text;
namespace StackOverflow
{
public class Rfc2898DeriveBytes : DeriveBytes
{
private const string DEFAULT_HASH_ALGORITHM = "SHA-1";
private const int MinimumSaltSize = 0;
private readonly byte[] _password;
private byte[] _salt;
private uint _iterations;
private HMAC _hmac;
private int _blockSize;
private byte[] _buffer;
private uint _block;
private int _startIndex;
private int _endIndex;
public string HashAlgorithm { get; }
public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations)
: this(password, salt, iterations, DEFAULT_HASH_ALGORITHM)
{
}
public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations, string hashAlgorithm)
{
if (salt == null)
throw new ArgumentNullException(nameof(salt));
if (salt.Length < MinimumSaltSize)
throw new ArgumentException(nameof(salt));
if (iterations <= 0)
throw new ArgumentOutOfRangeException(nameof(iterations));
if (password == null)
throw new NullReferenceException(); // This "should" be ArgumentNullException but for compat, we throw NullReferenceException.
_salt = (byte[])salt.Clone();
_iterations = (uint)iterations;
_password = (byte[])password.Clone();
HashAlgorithm = hashAlgorithm;
_hmac = OpenHmac();
// _blockSize is in bytes, HashSize is in bits.
_blockSize = _hmac.HashSize >> 3;
Initialize();
}
public Rfc2898DeriveBytes(string password, byte[] salt)
: this(password, salt, 1000)
{
}
public Rfc2898DeriveBytes(string password, byte[] salt, int iterations)
: this(password, salt, iterations, DEFAULT_HASH_ALGORITHM)
{
}
public Rfc2898DeriveBytes(string password, byte[] salt, int iterations, string hashAlgorithm)
: this(Encoding.UTF8.GetBytes(password), salt, iterations, hashAlgorithm)
{
}
public Rfc2898DeriveBytes(string password, int saltSize)
: this(password, saltSize, 1000)
{
}
public Rfc2898DeriveBytes(string password, int saltSize, int iterations)
: this(password, saltSize, iterations, DEFAULT_HASH_ALGORITHM)
{
}
public Rfc2898DeriveBytes(string password, int saltSize, int iterations, string hashAlgorithm)
{
if (saltSize < 0)
throw new ArgumentOutOfRangeException(nameof(saltSize));
if (saltSize < MinimumSaltSize)
throw new ArgumentException(nameof(saltSize));
if (iterations <= 0)
throw new ArgumentOutOfRangeException(nameof(iterations));
_salt = new byte[saltSize];
RandomNumberGenerator.Create().GetBytes(_salt);
_iterations = (uint)iterations;
_password = Encoding.UTF8.GetBytes(password);
HashAlgorithm = hashAlgorithm;
_hmac = OpenHmac();
// _blockSize is in bytes, HashSize is in bits.
_blockSize = _hmac.HashSize >> 3;
Initialize();
}
public int IterationCount
{
get
{
return (int)_iterations;
}
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value));
_iterations = (uint)value;
Initialize();
}
}
public byte[] Salt
{
get
{
return (byte[])_salt.Clone();
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.Length < MinimumSaltSize)
throw new ArgumentException("Too few bytes for salt");
_salt = (byte[])value.Clone();
Initialize();
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_hmac != null)
{
_hmac.Dispose();
_hmac = null;
}
if (_buffer != null)
Array.Clear(_buffer, 0, _buffer.Length);
if (_password != null)
Array.Clear(_password, 0, _password.Length);
if (_salt != null)
Array.Clear(_salt, 0, _salt.Length);
}
base.Dispose(disposing);
}
public override byte[] GetBytes(int cb)
{
if (cb <= 0)
throw new ArgumentOutOfRangeException(nameof(cb));
byte[] password = new byte[cb];
int offset = 0;
int size = _endIndex - _startIndex;
if (size > 0)
{
if (cb >= size)
{
Buffer.BlockCopy(_buffer, _startIndex, password, 0, size);
_startIndex = _endIndex = 0;
offset += size;
}
else
{
Buffer.BlockCopy(_buffer, _startIndex, password, 0, cb);
_startIndex += cb;
return password;
}
}
while (offset < cb)
{
byte[] T_block = Func();
int remainder = cb - offset;
if (remainder > _blockSize)
{
Buffer.BlockCopy(T_block, 0, password, offset, _blockSize);
offset += _blockSize;
}
else
{
Buffer.BlockCopy(T_block, 0, password, offset, remainder);
offset += remainder;
Buffer.BlockCopy(T_block, remainder, _buffer, _startIndex, _blockSize - remainder);
_endIndex += (_blockSize - remainder);
return password;
}
}
return password;
}
public byte[] CryptDeriveKey(string algname, string alghashname, int keySize, byte[] rgbIV)
{
// If this were to be implemented here, CAPI would need to be used (not CNG) because of
// unfortunate differences between the two. Using CNG would break compatibility. Since this
// assembly currently doesn't use CAPI it would require non-trivial additions.
// In addition, if implemented here, only Windows would be supported as it is intended as
// a thin wrapper over the corresponding native API.
// Note that this method is implemented in PasswordDeriveBytes (in the Csp assembly) using CAPI.
throw new PlatformNotSupportedException();
}
public override void Reset()
{
Initialize();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "HMACSHA1 is needed for compat. (https://github.com/dotnet/corefx/issues/9438)")]
private HMAC OpenHmac()
{
String hashAlgorithm = HashAlgorithm;
if (string.IsNullOrEmpty(hashAlgorithm))
throw new CryptographicException("HashAlgorithm name not present");
if (hashAlgorithm.Equals("SHA-1", StringComparison.OrdinalIgnoreCase))
return new HMACSHA1(_password);
if (hashAlgorithm.Equals("SHA-256", StringComparison.OrdinalIgnoreCase))
return new HMACSHA256(_password);
if (hashAlgorithm.Equals("SHA-384", StringComparison.OrdinalIgnoreCase))
return new HMACSHA384(_password);
if (hashAlgorithm.Equals("SHA-512", StringComparison.OrdinalIgnoreCase))
return new HMACSHA512(_password);
throw new CryptographicException("MAC algorithm " + hashAlgorithm + " not available");
}
private void Initialize()
{
if (_buffer != null)
Array.Clear(_buffer, 0, _buffer.Length);
_buffer = new byte[_blockSize];
_block = 1;
_startIndex = _endIndex = 0;
}
// This function is defined as follows:
// Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i)
// where i is the block number.
private byte[] Func()
{
byte[] temp = new byte[_salt.Length + sizeof(uint)];
Buffer.BlockCopy(_salt, 0, temp, 0, _salt.Length);
WriteInt(_block, temp, _salt.Length);
temp = _hmac.ComputeHash(temp);
byte[] ret = temp;
for (int i = 2; i <= _iterations; i++)
{
temp = _hmac.ComputeHash(temp);
for (int j = 0; j < _blockSize; j++)
{
ret[j] ^= temp[j];
}
}
// increment the block count.
_block++;
return ret;
}
private void WriteInt(uint i, byte[] buf, int bufOff)
{
buf[bufOff++] = (byte)(i >> 24);
buf[bufOff++] = (byte)(i >> 16);
buf[bufOff++] = (byte)(i >> 8);
buf[bufOff] = (byte)i;
}
}
}
this is a class where more specific exceptions have been rewritten, some specialized cloning is replaced, and the random salt generation is generalized. The minimum salt size has also been set to 0. Otherwise it is the same code in a different name space.
It is possible to use it like this:
string pw = "be9d3a4f1220495a96c38d36d8558365";
byte[] salt = new byte[0];
int iterations = 1024;
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(pw, salt, iterations, "SHA-256");
byte[] key = pbkdf2.GetBytes(16);
Note that the PIN is hexadecimals encoded as UTF-8, the default encoding for PBKDF2 (not the default encoding for .NET!). The result is a key that, when represented as hexadecimals equals 4369cb0560d54f55d0c03564fbd983c4.
I've converted to a 4.5 compatible class using a string to indicate the hash function, for the one with an enum HashAlgorithm (4.6 or something similar) take a look at the revision history.

C# Export Private/Public RSA key from RSACryptoServiceProvider to PEM string

I have an instance of System.Security.Cryptography.RSACryptoServiceProvider, i need to export it's key to a PEM string - like this:
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDUNPB6Lvx+tlP5QhSikADl71AjZf9KN31qrDpXNDNHEI0OTVJ1
OaP2l56bSKNo8trFne1NK/B4JzCuNP8x6oGCAG+7bFgkbTMzV2PCoDCRjNH957Q4
Gxgx1VoS6PjD3OigZnx5b9Hebbp3OrTuqNZaK/oLPGr5swxHILFVeHKupQIDAQAB
AoGAQk3MOZEGyZy0fjQ8eFKgRTfSBU1wR8Mwx6zKicbAotq0CBz2v7Pj3D+higlX
LYp7+rUOmUc6WoB8QGJEvlb0YZVxUg1yDLMWYPE7ddsHsOkBIs7zIyS6cqhn0yZD
VTRFjVST/EduvpUOL5hbyLSwuq+rbv0iPwGW5hkCHNEhx2ECQQDfLS5549wjiFXF
gcio8g715eMT+20we3YmgMJDcviMGwN/mArvnBgBQsFtCTsMoOxm68SfIrBYlKYy
BsFxn+19AkEA82q83pmcbGJRJ3ZMC/Pv+/+/XNFOvMkfT9qbuA6Lv69Z1yk7I1ie
FTH6tOmPUu4WsIOFtDuYbfV2pvpqx7GuSQJAK3SnvRIyNjUAxoF76fGgGh9WNPjb
DPqtSdf+e5Wycc18w+Z+EqPpRK2T7kBC4DWhcnTsBzSA8+6V4d3Q4ugKHQJATRhw
a3xxm65kD8CbA2omh0UQQgCVFJwKy8rsaRZKUtLh/JC1h1No9kOXKTeUSmrYSt3N
OjFp7OHCy84ihc8T6QJBANe+9xkN9hJYNK1pL1kSwXNuebzcgk3AMwHh7ThvjLgO
jruxbM2NyMM5tl9NZCgh1vKc2v5VaonqM1NBQPDeTTw=
-----END RSA PRIVATE KEY-----
But there is no such option according to the MSDN documentation, there is only some kind of XML export. I can't use any third party libraries like BouncyCastle.
Is there any way to generate this string?
Please note: The code below is for exporting a private key. If you are looking to export the public key, please refer to my answer given here.
The PEM format is simply the ASN.1 DER encoding of the key (per PKCS#1) converted to Base64. Given the limited number of fields needed to represent the key, it's pretty straightforward to create quick-and-dirty DER encoder to output the appropriate format then Base64 encode it. As such, the code that follows is not particularly elegant, but does the job:
private static void ExportPrivateKey(RSACryptoServiceProvider csp, TextWriter outputStream)
{
if (csp.PublicOnly) throw new ArgumentException("CSP does not contain a private key", "csp");
var parameters = csp.ExportParameters(true);
using (var stream = new MemoryStream())
{
var writer = new BinaryWriter(stream);
writer.Write((byte)0x30); // SEQUENCE
using (var innerStream = new MemoryStream())
{
var innerWriter = new BinaryWriter(innerStream);
EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version
EncodeIntegerBigEndian(innerWriter, parameters.Modulus);
EncodeIntegerBigEndian(innerWriter, parameters.Exponent);
EncodeIntegerBigEndian(innerWriter, parameters.D);
EncodeIntegerBigEndian(innerWriter, parameters.P);
EncodeIntegerBigEndian(innerWriter, parameters.Q);
EncodeIntegerBigEndian(innerWriter, parameters.DP);
EncodeIntegerBigEndian(innerWriter, parameters.DQ);
EncodeIntegerBigEndian(innerWriter, parameters.InverseQ);
var length = (int)innerStream.Length;
EncodeLength(writer, length);
writer.Write(innerStream.GetBuffer(), 0, length);
}
var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
outputStream.WriteLine("-----BEGIN RSA PRIVATE KEY-----");
// Output as Base64 with lines chopped at 64 characters
for (var i = 0; i < base64.Length; i += 64)
{
outputStream.WriteLine(base64, i, Math.Min(64, base64.Length - i));
}
outputStream.WriteLine("-----END RSA PRIVATE KEY-----");
}
}
private static void EncodeLength(BinaryWriter stream, int length)
{
if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
if (length < 0x80)
{
// Short form
stream.Write((byte)length);
}
else
{
// Long form
var temp = length;
var bytesRequired = 0;
while (temp > 0)
{
temp >>= 8;
bytesRequired++;
}
stream.Write((byte)(bytesRequired | 0x80));
for (var i = bytesRequired - 1; i >= 0; i--)
{
stream.Write((byte)(length >> (8 * i) & 0xff));
}
}
}
private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
{
stream.Write((byte)0x02); // INTEGER
var prefixZeros = 0;
for (var i = 0; i < value.Length; i++)
{
if (value[i] != 0) break;
prefixZeros++;
}
if (value.Length - prefixZeros == 0)
{
EncodeLength(stream, 1);
stream.Write((byte)0);
}
else
{
if (forceUnsigned && value[prefixZeros] > 0x7f)
{
// Add a prefix zero to force unsigned if the MSB is 1
EncodeLength(stream, value.Length - prefixZeros + 1);
stream.Write((byte)0);
}
else
{
EncodeLength(stream, value.Length - prefixZeros);
}
for (var i = prefixZeros; i < value.Length; i++)
{
stream.Write(value[i]);
}
}
}
With the current version of .NET, this can be done in a simple way.
RSA rsa = RSA.Create();
rsa.KeySize = 4096;
// Private key export.
string hdrPrv = "-----BEGIN RSA PRIVATE KEY-----";
string ftrPrv = "-----END RSA PRIVATE KEY-----";
string keyPrv = Convert.ToBase64String(rsa.ExportPkcs8PrivateKey());
string PEMPrv = "${hdrPrv}\n{keyPrv}\n{ftrPrv}";
// Public key export (with a correction for more accuracy from RobSiklos's comment to have the key in PKCS#1 RSAPublicKey format)
string hdrPub = "-----BEGIN RSA PUBLIC KEY-----";
string ftrPub = "-----END RSA PUBLIC KEY-----";
string keyPub = Convert.ToBase64String(rsa.ExportRSAPublicKey());
string PEMPub = "${hdrPub}\n{keyPub}\n{ftrPub}";
// Distribute PEMs.
Note: To have the nicely formatted file with new lines, you can write a little function to do it for you.
With the solution given above, you will have a file with only three lines.
If you're using .NET Core 3.0 this is already implemented out of the box
public string ExportPrivateKey(RSA rsa)
{
var privateKeyBytes = rsa.ExportRSAPrivateKey();
var builder = new StringBuilder("-----BEGIN RSA PRIVATE KEY");
builder.AppendLine("-----");
var base64PrivateKeyString = Convert.ToBase64String(privateKeyBytes);
var offset = 0;
const int LINE_LENGTH = 64;
while (offset < base64PrivateKeyString.Length)
{
var lineEnd = Math.Min(offset + LINE_LENGTH, base64PrivateKeyString.Length);
builder.AppendLine(base64PrivateKeyString.Substring(offset, lineEnd - offset));
offset = lineEnd;
}
builder.Append("-----END RSA PRIVATE KEY");
builder.AppendLine("-----");
return builder.ToString();
}
For anyone else who balked at the original answer's apparent complexity (which is very helpful, don't get me wrong), I thought I'd post my solution which is a little more straightforward IMO (but still based on the original answer):
public class RsaCsp2DerConverter {
private const int MaximumLineLength = 64;
// Based roughly on: http://stackoverflow.com/a/23739932/1254575
public RsaCsp2DerConverter() {
}
public byte[] ExportPrivateKey(String cspBase64Blob) {
if (String.IsNullOrEmpty(cspBase64Blob) == true)
throw new ArgumentNullException(nameof(cspBase64Blob));
var csp = new RSACryptoServiceProvider();
csp.ImportCspBlob(Convert.FromBase64String(cspBase64Blob));
if (csp.PublicOnly)
throw new ArgumentException("CSP does not contain a private key!", nameof(csp));
var parameters = csp.ExportParameters(true);
var list = new List<byte[]> {
new byte[] {0x00},
parameters.Modulus,
parameters.Exponent,
parameters.D,
parameters.P,
parameters.Q,
parameters.DP,
parameters.DQ,
parameters.InverseQ
};
return SerializeList(list);
}
private byte[] Encode(byte[] inBytes, bool useTypeOctet = true) {
int length = inBytes.Length;
var bytes = new List<byte>();
if (useTypeOctet == true)
bytes.Add(0x02); // INTEGER
bytes.Add(0x84); // Long format, 4 bytes
bytes.AddRange(BitConverter.GetBytes(length).Reverse());
bytes.AddRange(inBytes);
return bytes.ToArray();
}
public String PemEncode(byte[] bytes) {
if (bytes == null)
throw new ArgumentNullException(nameof(bytes));
var base64 = Convert.ToBase64String(bytes);
StringBuilder b = new StringBuilder();
b.Append("-----BEGIN RSA PRIVATE KEY-----\n");
for (int i = 0; i < base64.Length; i += MaximumLineLength)
b.Append($"{ base64.Substring(i, Math.Min(MaximumLineLength, base64.Length - i)) }\n");
b.Append("-----END RSA PRIVATE KEY-----\n");
return b.ToString();
}
private byte[] SerializeList(List<byte[]> list) {
if (list == null)
throw new ArgumentNullException(nameof(list));
var keyBytes = list.Select(e => Encode(e)).SelectMany(e => e).ToArray();
var binaryWriter = new BinaryWriter(new MemoryStream());
binaryWriter.Write((byte) 0x30); // SEQUENCE
binaryWriter.Write(Encode(keyBytes, false));
binaryWriter.Flush();
var result = ((MemoryStream) binaryWriter.BaseStream).ToArray();
binaryWriter.BaseStream.Dispose();
binaryWriter.Dispose();
return result;
}
}
Here is a GREAT NEWS, with .NET 7, Microsoft has added a new method to export RSA keys directly to PEM format.
Refer RSA.ExportRSAPrivateKeyPem Method
Below is how you can use it.
using (var rsa = new RSACryptoServiceProvider(2048)) // Generate a new 2048 bit RSA key
{
// RSA keys in PKCS#1 format, PEM encoded
string publicPrivateKeyPEM = rsa.ExportRSAPrivateKeyPem();
string publicOnlyKeyPEM = rsa.ExportRSAPublicKeyPem();
// RSA keys in XML format
string publicPrivateKeyXML = rsa.ToXmlString(true);
string publicOnlyKeyXML = rsa.ToXmlString(false);
// RSA keys in byte array
byte[] publicPrivateKey = rsa.ExportRSAPrivateKey();
byte[] publicOnlyKey = rsa.ExportRSAPublicKey();
}
public static Func<string, string> ToBase64PemFromKeyXMLString= (xmlPrivateKey) =>
{
if (string.IsNullOrEmpty(xmlPrivateKey))
throw new ArgumentNullException("RSA key must contains value!");
var keyContent = new PemReader(new StringReader(xmlPrivateKey));
if (keyContent == null)
throw new ArgumentNullException("private key is not valid!");
var ciphrPrivateKey = (AsymmetricCipherKeyPair)keyContent.ReadObject();
var asymmetricKey = new AsymmetricKeyEntry(ciphrPrivateKey.Private);
PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(asymmetricKey.Key);
var serializedPrivateKey = privateKeyInfo.ToAsn1Object().GetDerEncoded();
return Convert.ToBase64String(serializedPrivateKey);
};

Rfc2898 / PBKDF2 with SHA256 as digest in c#

I want to use Rfc2898 in c# to derive a key. I also need to use SHA256 as Digest for Rfc2898. I found the class Rfc2898DeriveBytes, but it uses SHA-1 and I don't see a way to make it use a different digest.
Is there a way to use Rfc2898 in c# with SHA256 as digest (short of implementing it from scratch)?
.NET Core has a new implementation of Rfc2898DeriveBytes.
The CoreFX version no longer has the the hashing algorithm hard-coded
The code is available on Github. It was merged to master on March 2017 and has been shipped with .NET Core 2.0.
For those who need it, .NET Framework 4.7.2 includes an overload of Rfc2898DeriveBytes that allows the hashing algorithm to be specified:
byte[] bytes;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
{
bytes = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}
The HashAlgorithmName options at the moment are:
MD5
SHA1
SHA256
SHA384
SHA512
See Bruno Garcia's answer.
At the time I started this answer, Rfc2898DeriveBytes was not configurable to use a different hash function. In the meantime, though, it has been improved; see Bruno Garcia's answer. The following function can be used to generate a hashed version of a user-provided password to store in a database for authentication purposes.
For users of older .NET frameworks, this is still useful:
// NOTE: The iteration count should
// be as high as possible without causing
// unreasonable delay. Note also that the password
// and salt are byte arrays, not strings. After use,
// the password and salt should be cleared (with Array.Clear)
public static byte[] PBKDF2Sha256GetBytes(int dklen, byte[] password, byte[] salt, int iterationCount){
using(var hmac=new System.Security.Cryptography.HMACSHA256(password)){
int hashLength=hmac.HashSize/8;
if((hmac.HashSize&7)!=0)
hashLength++;
int keyLength=dklen/hashLength;
if((long)dklen>(0xFFFFFFFFL*hashLength) || dklen<0)
throw new ArgumentOutOfRangeException("dklen");
if(dklen%hashLength!=0)
keyLength++;
byte[] extendedkey=new byte[salt.Length+4];
Buffer.BlockCopy(salt,0,extendedkey,0,salt.Length);
using(var ms=new System.IO.MemoryStream()){
for(int i=0;i<keyLength;i++){
extendedkey[salt.Length]=(byte)(((i+1)>>24)&0xFF);
extendedkey[salt.Length+1]=(byte)(((i+1)>>16)&0xFF);
extendedkey[salt.Length+2]=(byte)(((i+1)>>8)&0xFF);
extendedkey[salt.Length+3]=(byte)(((i+1))&0xFF);
byte[] u=hmac.ComputeHash(extendedkey);
Array.Clear(extendedkey,salt.Length,4);
byte[] f=u;
for(int j=1;j<iterationCount;j++){
u=hmac.ComputeHash(u);
for(int k=0;k<f.Length;k++){
f[k]^=u[k];
}
}
ms.Write(f,0,f.Length);
Array.Clear(u,0,u.Length);
Array.Clear(f,0,f.Length);
}
byte[] dk=new byte[dklen];
ms.Position=0;
ms.Read(dk,0,dklen);
ms.Position=0;
for(long i=0;i<ms.Length;i++){
ms.WriteByte(0);
}
Array.Clear(extendedkey,0,extendedkey.Length);
return dk;
}
}
The BCL Rfc2898DeriveBytes is hardcoded to use sha-1.
KeyDerivation.Pbkdf2 allows for exactly the same output, but it also allows HMAC SHA-256 and HMAC SHA-512. It's faster too; on my machine by around 5 times - and that's good for security, because it allows for more rounds, which makes life for crackers harder (incidentally sha-512 is a lot less gpu-friendly than sha-256 or sha1). And the api is simpler, to boot:
byte[] salt = ...
string password = ...
var rounds = 50000; // pick something bearable
var num_bytes_requested = 16; // 128 bits is fine
var prf = KeyDerivationPrf.HMACSHA512; // or sha256, or sha1
byte[] hashed = KeyDerivation.Pbkdf2(password, salt, prf, rounds, num_bytes_requested);
It's from the nuget package Microsoft.AspNetCore.Cryptography.KeyDerivation which does not depend on asp.net core; it runs on .net 4.5.1 or .net standard 1.3 or higher.
You could use Bouncy Castle. The C# specification lists the algorithm "PBEwithHmacSHA-256", which can only be PBKDF2 with SHA-256.
I know this is an old question, but for anyone that comes across it, you can now use KeyDerivation.Pbkdf2 from the Microsoft.AspNetCore.Cryptography.KeyDerivation nuget package. It is what is used in asp.net core.
Unfortunately it will add a ton of references that aren't really needed. You could just copy the code and paste it into your own project (although you will now have to maintain cryto code which is a PITA)
For what it's worth, here's a copy of Microsoft's implementation but with SHA-1 replaced with SHA512:
namespace System.Security.Cryptography
{
using System.Globalization;
using System.IO;
using System.Text;
[System.Runtime.InteropServices.ComVisible(true)]
public class Rfc2898DeriveBytes_HMACSHA512 : DeriveBytes
{
private byte[] m_buffer;
private byte[] m_salt;
private HMACSHA512 m_HMACSHA512; // The pseudo-random generator function used in PBKDF2
private uint m_iterations;
private uint m_block;
private int m_startIndex;
private int m_endIndex;
private static RNGCryptoServiceProvider _rng;
private static RNGCryptoServiceProvider StaticRandomNumberGenerator
{
get
{
if (_rng == null)
{
_rng = new RNGCryptoServiceProvider();
}
return _rng;
}
}
private const int BlockSize = 20;
//
// public constructors
//
public Rfc2898DeriveBytes_HMACSHA512(string password, int saltSize) : this(password, saltSize, 1000) { }
public Rfc2898DeriveBytes_HMACSHA512(string password, int saltSize, int iterations)
{
if (saltSize < 0)
throw new ArgumentOutOfRangeException("saltSize", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
byte[] salt = new byte[saltSize];
StaticRandomNumberGenerator.GetBytes(salt);
Salt = salt;
IterationCount = iterations;
m_HMACSHA512 = new HMACSHA512(new UTF8Encoding(false).GetBytes(password));
Initialize();
}
public Rfc2898DeriveBytes_HMACSHA512(string password, byte[] salt) : this(password, salt, 1000) { }
public Rfc2898DeriveBytes_HMACSHA512(string password, byte[] salt, int iterations) : this(new UTF8Encoding(false).GetBytes(password), salt, iterations) { }
public Rfc2898DeriveBytes_HMACSHA512(byte[] password, byte[] salt, int iterations)
{
Salt = salt;
IterationCount = iterations;
m_HMACSHA512 = new HMACSHA512(password);
Initialize();
}
//
// public properties
//
public int IterationCount
{
get { return (int)m_iterations; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
m_iterations = (uint)value;
Initialize();
}
}
public byte[] Salt
{
get { return (byte[])m_salt.Clone(); }
set
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length < 8)
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Cryptography_PasswordDerivedBytes_FewBytesSalt")));
m_salt = (byte[])value.Clone();
Initialize();
}
}
//
// public methods
//
public override byte[] GetBytes(int cb)
{
if (cb <= 0)
throw new ArgumentOutOfRangeException("cb", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
byte[] password = new byte[cb];
int offset = 0;
int size = m_endIndex - m_startIndex;
if (size > 0)
{
if (cb >= size)
{
Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, size);
m_startIndex = m_endIndex = 0;
offset += size;
}
else
{
Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, cb);
m_startIndex += cb;
return password;
}
}
//BCLDebug.Assert(m_startIndex == 0 && m_endIndex == 0, "Invalid start or end index in the internal buffer.");
while (offset < cb)
{
byte[] T_block = Func();
int remainder = cb - offset;
if (remainder > BlockSize)
{
Buffer.InternalBlockCopy(T_block, 0, password, offset, BlockSize);
offset += BlockSize;
}
else
{
Buffer.InternalBlockCopy(T_block, 0, password, offset, remainder);
offset += remainder;
Buffer.InternalBlockCopy(T_block, remainder, m_buffer, m_startIndex, BlockSize - remainder);
m_endIndex += (BlockSize - remainder);
return password;
}
}
return password;
}
public override void Reset()
{
Initialize();
}
private void Initialize()
{
if (m_buffer != null)
Array.Clear(m_buffer, 0, m_buffer.Length);
m_buffer = new byte[BlockSize];
m_block = 1;
m_startIndex = m_endIndex = 0;
}
internal static byte[] Int(uint i)
{
byte[] b = BitConverter.GetBytes(i);
byte[] littleEndianBytes = { b[3], b[2], b[1], b[0] };
return BitConverter.IsLittleEndian ? littleEndianBytes : b;
}
// This function is defined as follow :
// Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i)
// where i is the block number.
private byte[] Func()
{
byte[] INT_block = Int(m_block);
m_HMACSHA512.TransformBlock(m_salt, 0, m_salt.Length, m_salt, 0);
m_HMACSHA512.TransformFinalBlock(INT_block, 0, INT_block.Length);
byte[] temp = m_HMACSHA512.Hash;
m_HMACSHA512.Initialize();
byte[] ret = temp;
for (int i = 2; i <= m_iterations; i++)
{
temp = m_HMACSHA512.ComputeHash(temp);
for (int j = 0; j < BlockSize; j++)
{
ret[j] ^= temp[j];
}
}
// increment the block count.
m_block++;
return ret;
}
}
}
In addition to replacing HMACSHA1 with HMACSHA512, you need to add a StaticRandomNumberGenerator property because Utils.StaticRandomNumberGenerator is internal in the microsoft assembly, and you need to add the static byte[] Int(uint i) method because microsoft's Utils.Int is also internal. Other than that, the code works.
Although this is an old question, since I added reference to this question in my Question Configurable Rfc2898DeriveBytes where I asked whether a generic implementation of the Rfc2898DeriveBytes algorithm was correct.
I have now tested and validated that it generates the exact same hash values if HMACSHA1 is provided for TAlgorithm as the .NET implementation of Rfc2898DeriveBytes
In order to use the class, one must provide the constructor for the HMAC algorithm requiring a byte array as the first argument.
e.g.:
var rfcGenSha1 = new Rfc2898DeriveBytes<HMACSHA1>(b => new HMACSHA1(b), key, ...)
var rfcGenSha256 = new Rfc2898DeriveBytes<HMACSHA256>(b => new HMACSHA256(b), key, ...)
This requires the algorithm to inherit HMAC at this point, I'm believe one might be able to Reduce the restriction to require inheritance from KeyedHashAlgorithm instead of HMAC, as long as the constructor of the algorithm accepts an array of bytes to the constructor.

c# equivalent of "java.security.spec.RSAPublicKeySpec" and "java.security.PublicKey"

I'm developing a new version in c# of an existing java application.
The existing application uses RSA encryption with java.security.spec.* and boncycastle api.
I'm looking for equivalent code in c# for the java below code:
public static java.security.PublicKey getKey
(
org.bouncycastle.asn1.x509.RSAPublicKeyStructure rsaPublicKey
)
{
java.security.KeyFactory keyFactory = KeyFactory.getInstance("RSA");
java.security.spec.RSAPublicKeySpec keySpec = new RSAPublicKeySpec(
rsaPublicKey.getModulus(),
rsaPublicKey.getPublicExponent());
java.security.PublicKey pkey = keyFactory.generatePublic(keySpec);
return pkey;
}
I "googled" a lot but don't found solution.
Thanks in advance for your help.
Although you may be already aware of this, there is a .NET version of Bouncy Castle, so you can use it in your C# project.
Regarding your question, here is an example of implementing signing in pure Bouncy Castle, an it deals with key generation in the MakeKey method, so you may want to take a look at it.
By the way, if this key is in a certificate, you may want to look at the .NET X509Certificate2 class.
Edit
I tried to convert your method into a c# equivalent, and this it the closer I got:
public static byte[] getKey(Org.BouncyCastle.Asn1.x509.RSAPublicKeyStructure rsaPublicKey)
{
Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters bcKeySpec = new RsaKeyParameters();
bcKeySpec.RsaKeyParameters(false, rsaPublicKey.getModulus(), rsaPublicKey.getPublicExponent());
RSAParameters keySpec = Org.BouncyCastle.Security.DotNetUtilities.ToRSAParameters(bcKeySpec);
RSACryptoServiceProvider keyFactory = new RSACryptoServiceProvider();
keyFactory.ImportParameters(keySpec);
byte[] pKey = keyFactory.ExportCspBlob(false);
return pKey;
}
Note that the key is exported into a byte array, which depending of what you want to do with your key later, may or may not be helpful to you, also, the RSACryptoServiceProvider object let you encrypt, decrypt, sign and verify, so if you are going to get the key for any of these purposes, then you may want to return the keyFactory object instead of the exported public key.
If you want more information about RSACryptoServiceProvider you can read here: http://msdn.microsoft.com/en-us/library/s575f7e2.aspx
public static string EncryptRsa(string stringPublicKey, string stringDataToEncrypt)
{
byte[] publicKey = Convert.FromBase64String(stringPublicKey);
using (RSACryptoServiceProvider rsa = DecodeX509PublicKey(publicKey))
{
byte[] dataToEncrypt = Encoding.UTF8.GetBytes(stringDataToEncrypt);
byte[] encryptedData = rsa.Encrypt(dataToEncrypt, false);
return Convert.ToBase64String(encryptedData);
}
}
public static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509key)
{
byte[] SeqOID = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
MemoryStream ms = new MemoryStream(x509key);
BinaryReader reader = new BinaryReader(ms);
if (reader.ReadByte() == 0x30)
ReadASNLength(reader); //skip the size
else
return null;
int identifierSize = 0; //total length of Object Identifier section
if (reader.ReadByte() == 0x30)
identifierSize = ReadASNLength(reader);
else
return null;
if (reader.ReadByte() == 0x06) //is the next element an object identifier?
{
int oidLength = ReadASNLength(reader);
byte[] oidBytes = new byte[oidLength];
reader.Read(oidBytes, 0, oidBytes.Length);
if (oidBytes.SequenceEqual(SeqOID) == false) //is the object identifier rsaEncryption PKCS#1?
return null;
int remainingBytes = identifierSize - 2 - oidBytes.Length;
reader.ReadBytes(remainingBytes);
}
if (reader.ReadByte() == 0x03) //is the next element a bit string?
{
ReadASNLength(reader); //skip the size
reader.ReadByte(); //skip unused bits indicator
if (reader.ReadByte() == 0x30)
{
ReadASNLength(reader); //skip the size
if (reader.ReadByte() == 0x02) //is it an integer?
{
int modulusSize = ReadASNLength(reader);
byte[] modulus = new byte[modulusSize];
reader.Read(modulus, 0, modulus.Length);
if (modulus[0] == 0x00) //strip off the first byte if it's 0
{
byte[] tempModulus = new byte[modulus.Length - 1];
Array.Copy(modulus, 1, tempModulus, 0, modulus.Length - 1);
modulus = tempModulus;
}
if (reader.ReadByte() == 0x02) //is it an integer?
{
int exponentSize = ReadASNLength(reader);
byte[] exponent = new byte[exponentSize];
reader.Read(exponent, 0, exponent.Length);
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1024);
RSAParameters RSAKeyInfo = new RSAParameters();
RSAKeyInfo.Modulus = modulus;
RSAKeyInfo.Exponent = exponent;
RSA.ImportParameters(RSAKeyInfo);
return RSA;
}
}
}
}
return null;
}
public static int ReadASNLength(BinaryReader reader)
{
//Note: this method only reads lengths up to 4 bytes long as
//this is satisfactory for the majority of situations.
int length = reader.ReadByte();
if ((length & 0x00000080) == 0x00000080) //is the length greater than 1 byte
{
int count = length & 0x0000000f;
byte[] lengthBytes = new byte[4];
reader.Read(lengthBytes, 4 - count, count);
Array.Reverse(lengthBytes); //
length = BitConverter.ToInt32(lengthBytes, 0);
}
return length;
}

Categories

Resources