Support for Ed25519 Host- and Private-Keys

This commit is contained in:
Stefan Rinkes
2018-11-27 15:46:59 +01:00
committed by Stefan Rinkes
parent 90fb62b7cc
commit c9f9e3ca35
13 changed files with 1434 additions and 6 deletions
+18
View File
@@ -53,3 +53,21 @@ SHA512
written by Christian Winnerlein (CodesInChaos)
public domain
directly from the specification
License notice for BCrypt
-------------------------
Copyright (c) 2006 Damien Miller <djm@mindrot.org>
Copyright (c) 2010 Ryan D. Emerle
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -824,6 +824,9 @@
<Compile Include="..\Renci.SshNet\Security\Cryptography\AsymmetricCipher.cs">
<Link>Security\Cryptography\AsymmetricCipher.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\Security\Cryptography\Bcrypt.cs">
<Link>Security\Cryptography\Bcrypt.cs</Link>
</Compile>
<Compile Include="..\Renci.SshNet\Security\Cryptography\BlockCipher.cs">
<Link>Security\Cryptography\BlockCipher.cs</Link>
</Compile>
@@ -545,6 +545,34 @@ namespace Renci.SshNet.Tests.Classes
}
}
/// <summary>
/// A test for opening an openssh v1 keyfile where there is no passphrase.
///</summary>
[TestMethod()]
[Owner("bhalbright")]
[TestCategory("PrivateKey")]
public void TestOpenSshV1KeyFileNoPassphrase()
{
using (var stream = GetData("Key.OPENSSH.ED25519.txt"))
{
new PrivateKeyFile(stream);
}
}
/// <summary>
/// A test for opening an openssh v1 keyfile where there is a passphrase.
///</summary>
[TestMethod()]
[Owner("bhalbright")]
[TestCategory("PrivateKey")]
public void TestOpenSshV1KeyFileWithPassphrase()
{
using (var stream = GetData("Key.OPENSSH.ED25519.Encrypted.txt"))
{
new PrivateKeyFile(stream, "password");
}
}
private void SaveStreamToFile(Stream stream, string fileName)
{
var buffer = new byte[4000];
@@ -567,4 +595,4 @@ namespace Renci.SshNet.Tests.Classes
return tempFile;
}
}
}
}
@@ -0,0 +1,9 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABBg
HWh+J0IG6OfYxD74SoT9AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIGFd
yflleGqSPOhgSYZf7ZQFlG0zEL9VDGC69UbtaaByAAAAoDLm8u8wFwlqjzZRfVxj
wzGTYFJFtfkHRqfFBE4xKgknHNRbCT1OQb7rgE7nZbUXIlb1NCTZLbXti9AYNZpz
ycvPD4Dc6lB03b8pNHoFVSkrCwxrWB5bKtIM4OZNcDK1lZDBEWE2aZXf9puRHbu3
ccrK/F5GjRi2pUa8qnfqThN1mNPZwFTx4oSKeRaUMdeHBrNwDtaxq32A6Q4KHoYO
KPM=
-----END OPENSSH PRIVATE KEY-----
@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
c2gtZWQyNTUxOQAAACANCWZw0K8UGXDQC32WBuyzwFtTGBBr1VuZ43uzpTBjIgAA
AKBATgCiQE4AogAAAAtzc2gtZWQyNTUxOQAAACANCWZw0K8UGXDQC32WBuyzwFtT
GBBr1VuZ43uzpTBjIgAAAEAAzBF1MPUxrs+ycpJh28zzo/F3m6WcKO+orsSbR5Lw
KQ0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7OlMGMiAAAAFGVkMjU1MTkta2V5
LTIwMTgxMTI3AQIDBAUGBwgJ
-----END OPENSSH PRIVATE KEY-----
@@ -719,6 +719,10 @@
<EmbeddedResource Include="Data\Key.ECDSA384.Encrypted.txt" />
<EmbeddedResource Include="Data\Key.ECDSA521.Encrypted.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Data\Key.OPENSSH.ED25519.Encrypted.txt" />
<EmbeddedResource Include="Data\Key.OPENSSH.ED25519.txt" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
+1
View File
@@ -378,6 +378,7 @@ namespace Renci.SshNet
HostKeyAlgorithms = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>
{
{"ssh-ed25519", data => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(), data)},
#if FEATURE_ECDSA
{"ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(), data)},
{"ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(), data)},
+154 -3
View File
@@ -11,6 +11,7 @@ using Renci.SshNet.Security.Cryptography.Ciphers;
using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
using Renci.SshNet.Security.Cryptography.Ciphers.Paddings;
using System.Diagnostics.CodeAnalysis;
using Renci.SshNet.Security.Cryptography;
namespace Renci.SshNet
{
@@ -25,13 +26,16 @@ namespace Renci.SshNet
/// The following private keys are supported:
/// <list type="bullet">
/// <item>
/// <description>RSA in OpenSSH and ssh.com format</description>
/// <description>RSA in OpenSSL PEM and ssh.com format</description>
/// </item>
/// <item>
/// <description>DSA in OpenSSH and ssh.com format</description>
/// <description>DSA in OpenSSL PEM and ssh.com format</description>
/// </item>
/// <item>
/// <description>ECDSA 256/384/521 in OpenSSH format</description>
/// <description>ECDSA 256/384/521 in OpenSSL PEM format</description>
/// </item>
/// <item>
/// <description>ED25519 in OpenSSH key format</description>
/// </item>
/// </list>
/// </para>
@@ -214,6 +218,10 @@ namespace Renci.SshNet
HostKey = new KeyHostAlgorithm(_key.ToString(), _key);
break;
#endif
case "OPENSSH":
_key = ParseOpenSshV1Key(decryptedData, passPhrase);
HostKey = new KeyHostAlgorithm(_key.ToString(), _key);
break;
case "SSH2 ENCRYPTED":
var reader = new SshDataReader(decryptedData);
var magicNumber = reader.ReadUInt32();
@@ -358,6 +366,144 @@ namespace Renci.SshNet
return cipher.Decrypt(cipherData);
}
/// <summary>
/// Parses an OpenSSH V1 key file (i.e. ED25519 key) according to the the key spec:
/// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key.
/// </summary>
/// <param name="keyFileData">the key file data (i.e. base64 encoded data between the header/footer)</param>
/// <param name="passPhrase">passphrase or null if there isn't one</param>
/// <returns></returns>
private ED25519Key ParseOpenSshV1Key(byte [] keyFileData, string passPhrase)
{
var keyReader = new SshDataReader(keyFileData);
//check magic header
var authMagic = Encoding.UTF8.GetBytes("openssh-key-v1\0");
var keyHeaderBytes = keyReader.ReadBytes(authMagic.Length);
if (!authMagic.IsEqualTo(keyHeaderBytes))
{
throw new SshException("This openssh key does not contain the 'openssh-key-v1' format magic header");
}
//cipher will be "aes256-cbc" if using a passphrase, "none" otherwise
var cipherName = keyReader.ReadString(Encoding.UTF8);
//key derivation function (kdf): bcrypt or nothing
var kdfName = keyReader.ReadString(Encoding.UTF8);
//kdf options length: 24 if passphrase, 0 if no passphrase
var kdfOptionsLen = (int)keyReader.ReadUInt32();
byte[] salt = null;
int rounds = 0;
if (kdfOptionsLen > 0)
{
var saltLength = (int)keyReader.ReadUInt32();
salt = keyReader.ReadBytes(saltLength);
rounds = (int)keyReader.ReadUInt32();
}
//number of public keys, only supporting 1 for now
var numberOfPublicKeys = (int)keyReader.ReadUInt32();
if (numberOfPublicKeys != 1)
{
throw new SshException("At this time only one public key in the openssh key is supported.");
}
//length of first public key section
keyReader.ReadUInt32();
var keyType = keyReader.ReadString(Encoding.UTF8);
if(keyType != "ssh-ed25519")
{
throw new SshException("openssh key type: " + keyType + " is not supported");
}
//read public key
var publicKeyLength = (int)keyReader.ReadUInt32(); //32
var publicKey = keyReader.ReadBytes(publicKeyLength);
//possibly encrypted private key
var privateKeyLength = (int)keyReader.ReadUInt32();
var privateKeyBytes = keyReader.ReadBytes(privateKeyLength);
//decrypt private key if necessary
if (cipherName == "aes256-cbc")
{
if (string.IsNullOrEmpty(passPhrase))
{
throw new SshPassPhraseNullOrEmptyException("Private key is encrypted but passphrase is empty.");
}
if (string.IsNullOrEmpty(kdfName) || kdfName != "bcrypt")
{
throw new SshException("kdf " + kdfName + " is not supported for openssh key file");
}
//inspired by the SSHj library (https://github.com/hierynomus/sshj)
//apply the kdf to derive a key and iv from the passphrase
var passPhraseBytes = Encoding.UTF8.GetBytes(passPhrase);
byte[] keyiv = new byte[48];
new BCrypt().Pbkdf(passPhraseBytes, salt, rounds, keyiv);
byte[] key = new byte[32];
Array.Copy(keyiv, 0, key, 0, 32);
byte[] iv = new byte[16];
Array.Copy(keyiv, 32, iv, 0, 16);
//now that we have the key/iv, use a cipher to decrypt the bytes
var cipher = new AesCipher(key, new CbcCipherMode(iv), new PKCS7Padding());
privateKeyBytes = cipher.Decrypt(privateKeyBytes);
}
else if (cipherName != "none")
{
throw new SshException("cipher name " + cipherName + " for openssh key file is not supported");
}
//validate private key length
privateKeyLength = privateKeyBytes.Length;
if (privateKeyLength % 8 != 0)
{
throw new SshException("The private key section must be a multiple of the block size (8)");
}
//now parse the data we called the private key, it actually contains the public key again
//so we need to parse through it to get the private key bytes, plus there's some
//validation we need to do.
var privateKeyReader = new SshDataReader(privateKeyBytes);
//check ints should match, they wouldn't match for example if the wrong passphrase was supplied
int checkInt1 = (int)privateKeyReader.ReadUInt32();
int checkInt2 = (int)privateKeyReader.ReadUInt32();
if (checkInt1 != checkInt2)
{
throw new SshException("The checkints differed, the openssh key was not correctly decoded.");
}
//key type, we already know it is ssh-ed25519
privateKeyReader.ReadString(Encoding.UTF8);
//public key length/bytes (again)
var publicKeyLength2 = (int)privateKeyReader.ReadUInt32();
privateKeyReader.ReadBytes(publicKeyLength2);
//length of private and public key (64)
privateKeyReader.ReadUInt32();
var unencryptedPrivateKey = privateKeyReader.ReadBytes(32);
//public key (again)
privateKeyReader.ReadBytes(32);
//comment, we don't need this but we could log it, not sure if necessary
var comment = privateKeyReader.ReadString(Encoding.UTF8);
//The list of privatekey/comment pairs is padded with the bytes 1, 2, 3, ...
//until the total length is a multiple of the cipher block size.
var padding = privateKeyReader.ReadBytes();
for (int i = 0; i < padding.Length; i++)
{
if ((int)padding[i] != i + 1)
{
throw new SshException("Padding of openssh key format contained wrong byte at position: " + i);
}
}
return new ED25519Key(publicKey.Reverse(), unencryptedPrivateKey);
}
#region IDisposable Members
private bool _isDisposed;
@@ -426,6 +572,11 @@ namespace Renci.SshNet
return base.ReadBytes(length);
}
public new byte[] ReadBytes()
{
return base.ReadBytes();
}
/// <summary>
/// Reads next mpint data type from internal buffer where length specified in bits.
/// </summary>
+4 -1
View File
@@ -301,8 +301,11 @@
<Compile Include="Security\Chaos.NaCl\Internal\Sha512Internal.cs" />
<Compile Include="Security\Chaos.NaCl\MontgomeryCurve25519.cs" />
<Compile Include="Security\Chaos.NaCl\Sha512.cs" />
<Compile Include="Security\Cryptography\ED25519DigitalSignature.cs" />
<Compile Include="Security\Cryptography\EcdsaDigitalSignature.cs" />
<Compile Include="Security\Cryptography\EcdsaKey.cs" />
<Compile Include="Security\Cryptography\ED25519Key.cs" />
<Compile Include="Security\Cryptography\Bcrypt.cs" />
<Compile Include="Security\Cryptography\HMACMD5.cs" />
<Compile Include="Security\Cryptography\HMACSHA1.cs" />
<Compile Include="Security\Cryptography\HMACSHA256.cs" />
@@ -620,4 +623,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>
@@ -0,0 +1,956 @@
//
// Copyright (c) 2006 Damien Miller <djm@mindrot.org>
// Copyright (c) 2010 Ryan D. Emerle
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
using Renci.SshNet.Abstractions;
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
namespace Renci.SshNet.Security.Cryptography
{
/// <summary>BCrypt implementation.</summary>
/// <remarks>
/// <para>
/// BCrypt implements OpenBSD-style Blowfish password hashing using the scheme described in
/// <a href="http://www.usenix.org/event/usenix99/provos/provos_html/index.html">"A Future-
/// Adaptable Password Scheme"</a> by Niels Provos and David Mazieres.
/// </para>
/// <para>
/// This password hashing system tries to thwart off-line password cracking using a
/// computationally-intensive hashing algorithm, based on Bruce Schneier's Blowfish cipher.
/// The work factor of the algorithm is parameterised, so it can be increased as computers
/// get faster.
/// </para>
/// <para>
/// Usage is really simple. To hash a password for the first time, call the <see
/// cref="HashPassword(string)"/> method with a random salt, like this:
/// </para>
/// <code>string pw_hash = BCrypt.HashPassword(plain_password);</code>
/// <para>
/// To check whether a plaintext password matches one that has been hashed previously,
/// use the <see cref="Verify"/> method:
/// </para>
/// <code>
/// if (BCrypt.Verify(candidate_password, stored_hash))
/// Console.WriteLine("It matches");
/// else
/// Console.WriteLine("It does not match");
/// </code>
/// <para>
/// The <see cref="GenerateSalt()"/> method takes an optional parameter (workFactor) that
/// determines the computational complexity of the hashing:
/// </para>
/// <code>
/// string strong_salt = BCrypt.GenerateSalt(10);
/// string stronger_salt = BCrypt.GenerateSalt(12);
/// </code>
/// <para>
/// The amount of work increases exponentially (2^workFactor), so each increment is twice
/// as much work. The default workFactor is 10, and the valid range is 4 to 31.
/// </para>
/// </remarks>
internal sealed class BCrypt
{
// BCrypt parameters
private const int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
private const int BCRYPT_SALT_LEN = 16;
// Blowfish parameters
private const int BLOWFISH_NUM_ROUNDS = 16;
// Initial contents of key schedule
private static readonly uint[] _POrig = {
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b
};
private static readonly uint[] _SOrig = {
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
};
// OpenBSD IV: "OxychromaticBlowfishSwatDynamite" in big endian
private static readonly uint[] _OpenBsdIv = new uint[] {
0x4f787963, 0x68726f6d, 0x61746963, 0x426c6f77,
0x66697368, 0x53776174, 0x44796e61, 0x6d697465,
};
// bcrypt IV: "OrpheanBeholderScryDoubt"
private static readonly uint[] _BfCryptCiphertext = {
0x4f727068, 0x65616e42, 0x65686f6c,
0x64657253, 0x63727944, 0x6f756274
};
// Table for Base64 encoding
private static readonly char[] _Base64Code = {
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9'
};
// Table for Base64 decoding
private static readonly int[] _Index64 = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
-1, -1, -1, -1, -1, -1, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, -1, -1, -1, -1, -1
};
// Expanded Blowfish key
private uint[] _P;
private uint[] _S;
/// <summary>
/// Hash a string using the OpenBSD bcrypt scheme and a salt generated by <see
/// cref="BCrypt.GenerateSalt()"/>.
/// </summary>
/// <remarks>Just an alias for HashPassword.</remarks>
/// <param name="source">The string to hash.</param>
/// <returns>The hashed string.</returns>
public static string HashString(string source)
{
return HashPassword(source);
}
/// <summary>
/// Hash a string using the OpenBSD bcrypt scheme and a salt generated by <see
/// cref="BCrypt.GenerateSalt()"/>.
/// </summary>
/// <remarks>Just an alias for HashPassword.</remarks>
/// <param name="source"> The string to hash.</param>
/// <param name="workFactor">The log2 of the number of rounds of hashing to apply - the work
/// factor therefore increases as 2^workFactor.</param>
/// <returns>The hashed string.</returns>
public static string HashString(string source, int workFactor)
{
return HashPassword(source, GenerateSalt(workFactor));
}
/// <summary>
/// Hash a password using the OpenBSD bcrypt scheme and a salt generated by <see
/// cref="BCrypt.GenerateSalt()"/>.
/// </summary>
/// <param name="input">The password to hash.</param>
/// <returns>The hashed password.</returns>
public static string HashPassword(string input)
{
return HashPassword(input, GenerateSalt());
}
/// <summary>
/// Hash a password using the OpenBSD bcrypt scheme and a salt generated by <see
/// cref="BCrypt.GenerateSalt(int)"/> using the given <paramref name="workFactor"/>.
/// </summary>
/// <param name="input"> The password to hash.</param>
/// <param name="workFactor">The log2 of the number of rounds of hashing to apply - the work
/// factor therefore increases as 2^workFactor.</param>
/// <returns>The hashed password.</returns>
public static string HashPassword(string input, int workFactor)
{
return HashPassword(input, GenerateSalt(workFactor));
}
/// <summary>Hash a password using the OpenBSD bcrypt scheme.</summary>
/// <exception cref="ArgumentException">Thrown when one or more arguments have unsupported or
/// illegal values.</exception>
/// <param name="input">The password to hash.</param>
/// <param name="salt"> the salt to hash with (perhaps generated using BCrypt.gensalt).</param>
/// <returns>The hashed password</returns>
public static string HashPassword(string input, string salt)
{
if (input == null)
throw new ArgumentNullException("input");
if (string.IsNullOrEmpty(salt))
throw new ArgumentException("Invalid salt", "salt");
// Determinthe starting offset and validate the salt
int startingOffset;
char minor = (char)0;
if (salt[0] != '$' || salt[1] != '2')
throw new SaltParseException("Invalid salt version");
if (salt[2] == '$')
startingOffset = 3;
else
{
minor = salt[2];
if (minor != 'a' || salt[3] != '$')
throw new SaltParseException("Invalid salt revision");
startingOffset = 4;
}
// Extract number of rounds
if (salt[startingOffset + 2] > '$')
throw new SaltParseException("Missing salt rounds");
// Extract details from salt
int logRounds = Convert.ToInt32(salt.Substring(startingOffset, 2));
string extractedSalt = salt.Substring(startingOffset + 3, 22);
byte[] inputBytes = Encoding.UTF8.GetBytes((input + (minor >= 'a' ? "\0" : "")));
byte[] saltBytes = DecodeBase64(extractedSalt, BCRYPT_SALT_LEN);
BCrypt bCrypt = new BCrypt();
byte[] hashed = bCrypt.CryptRaw(inputBytes, saltBytes, logRounds);
// Generate result string
StringBuilder result = new StringBuilder();
result.Append("$2");
if (minor >= 'a')
result.Append(minor);
result.AppendFormat("${0:00}$", logRounds);
result.Append(EncodeBase64(saltBytes, saltBytes.Length));
result.Append(EncodeBase64(hashed, (_BfCryptCiphertext.Length * 4) - 1));
return result.ToString();
}
/// <summary>
/// Generate a salt for use with the <see cref="BCrypt.HashPassword(string,string)"/> method.
/// </summary>
/// <param name="workFactor">The log2 of the number of rounds of hashing to apply - the work
/// factor therefore increases as 2**workFactor.</param>
/// <returns>A base64 encoded salt value.</returns>
public static string GenerateSalt(int workFactor)
{
if (workFactor < 4 || workFactor > 31)
throw new ArgumentOutOfRangeException("workFactor", "The work factor must be between 4 and 31 (inclusive)");
byte[] rnd = new byte[BCRYPT_SALT_LEN];
#if FEATURE_RNG_CREATE
RandomNumberGenerator rng = RandomNumberGenerator.Create();
#elif FEATURE_RNG_CSP
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
#endif
#if FEATURE_RNG_CREATE || FEATURE_RNG_CSP
rng.GetBytes(rnd);
#endif
StringBuilder rs = new StringBuilder();
rs.AppendFormat("$2a${0:00}$", workFactor);
rs.Append(EncodeBase64(rnd, rnd.Length));
return rs.ToString();
}
/// <summary>
/// Generate a salt for use with the <see cref="BCrypt.HashPassword(string,string)"/> method
/// selecting a reasonable default for the number of hashing rounds to apply.
/// </summary>
/// <returns>A base64 encoded salt value.</returns>
public static string GenerateSalt()
{
return GenerateSalt(GENSALT_DEFAULT_LOG2_ROUNDS);
}
/// <summary>
/// Verifies that the hash of the given <paramref name="text"/> matches the provided
/// <paramref name="hash"/>
/// </summary>
/// <param name="text">The text to verify.</param>
/// <param name="hash"> The previously-hashed password.</param>
/// <returns>true if the passwords match, false otherwise.</returns>
public static bool Verify(string text, string hash)
{
return hash == HashPassword(text, hash);
}
/// <summary>
/// Encode a byte array using bcrypt's slightly-modified base64 encoding scheme. Note that this
/// is *not* compatible with the standard MIME-base64 encoding.
/// </summary>
/// <exception cref="ArgumentException">Thrown when one or more arguments have unsupported or
/// illegal values.</exception>
/// <param name="byteArray">The byte array to encode.</param>
/// <param name="length"> The number of bytes to encode.</param>
/// <returns>Base64-encoded string.</returns>
private static string EncodeBase64(byte[] byteArray, int length)
{
if (length <= 0 || length > byteArray.Length)
throw new ArgumentException("Invalid length", "length");
int off = 0;
StringBuilder rs = new StringBuilder();
while (off < length)
{
int c1 = byteArray[off++] & 0xff;
rs.Append(_Base64Code[(c1 >> 2) & 0x3f]);
c1 = (c1 & 0x03) << 4;
if (off >= length)
{
rs.Append(_Base64Code[c1 & 0x3f]);
break;
}
int c2 = byteArray[off++] & 0xff;
c1 |= (c2 >> 4) & 0x0f;
rs.Append(_Base64Code[c1 & 0x3f]);
c1 = (c2 & 0x0f) << 2;
if (off >= length)
{
rs.Append(_Base64Code[c1 & 0x3f]);
break;
}
c2 = byteArray[off++] & 0xff;
c1 |= (c2 >> 6) & 0x03;
rs.Append(_Base64Code[c1 & 0x3f]);
rs.Append(_Base64Code[c2 & 0x3f]);
}
return rs.ToString();
}
/// <summary>
/// Decode a string encoded using bcrypt's base64 scheme to a byte array. Note that this is *not*
/// compatible with the standard MIME-base64 encoding.
/// </summary>
/// <exception cref="ArgumentException">Thrown when one or more arguments have unsupported or
/// illegal values.</exception>
/// <param name="encodedstring">The string to decode.</param>
/// <param name="maximumBytes"> The maximum bytes to decode.</param>
/// <returns>The decoded byte array.</returns>
private static byte[] DecodeBase64(string encodedstring, int maximumBytes)
{
int position = 0,
sourceLength = encodedstring.Length,
outputLength = 0;
if (maximumBytes <= 0)
throw new ArgumentException("Invalid maximum bytes value", "maximumBytes");
// TODO: update to use a List<byte> - it's only ever 16 bytes, so it's not a big deal
StringBuilder rs = new StringBuilder();
while (position < sourceLength - 1 && outputLength < maximumBytes)
{
int c1 = Char64(encodedstring[position++]);
int c2 = Char64(encodedstring[position++]);
if (c1 == -1 || c2 == -1)
break;
rs.Append((char)((c1 << 2) | ((c2 & 0x30) >> 4)));
if (++outputLength >= maximumBytes || position >= sourceLength)
break;
int c3 = Char64(encodedstring[position++]);
if (c3 == -1)
break;
rs.Append((char)(((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2)));
if (++outputLength >= maximumBytes || position >= sourceLength)
break;
int c4 = Char64(encodedstring[position++]);
rs.Append((char)(((c3 & 0x03) << 6) | c4));
++outputLength;
}
byte[] ret = new byte[outputLength];
for (position = 0; position < outputLength; position++)
ret[position] = (byte)rs[position];
return ret;
}
/// <summary>
/// Look up the 3 bits base64-encoded by the specified character, range-checking against
/// conversion table.
/// </summary>
/// <param name="character">The base64-encoded value.</param>
/// <returns>The decoded value of x.</returns>
private static int Char64(char character)
{
if (character < 0 || character > _Index64.Length)
return -1;
return _Index64[character];
}
/// <summary>Blowfish encipher a single 64-bit block encoded as two 32-bit halves.</summary>
/// <param name="blockArray">An array containing the two 32-bit half blocks.</param>
/// <param name="offset"> The position in the array of the blocks.</param>
private void Encipher(uint[] blockArray, int offset)
{
uint round,
n,
block = blockArray[offset],
r = blockArray[offset + 1];
block ^= _P[0];
for (round = 0; round <= BLOWFISH_NUM_ROUNDS - 2;)
{
// Feistel substitution on left word
n = _S[(block >> 24) & 0xff];
n += _S[0x100 | ((block >> 16) & 0xff)];
n ^= _S[0x200 | ((block >> 8) & 0xff)];
n += _S[0x300 | (block & 0xff)];
r ^= n ^ _P[++round];
// Feistel substitution on right word
n = _S[(r >> 24) & 0xff];
n += _S[0x100 | ((r >> 16) & 0xff)];
n ^= _S[0x200 | ((r >> 8) & 0xff)];
n += _S[0x300 | (r & 0xff)];
block ^= n ^ _P[++round];
}
blockArray[offset] = r ^ _P[BLOWFISH_NUM_ROUNDS + 1];
blockArray[offset + 1] = block;
}
/// <summary>Cycically extract a word of key material.</summary>
/// <param name="data">The string to extract the data from.</param>
/// <param name="offset"> [in,out] The current offset.</param>
/// <returns>The next word of material from data.</returns>
private static uint StreamToWord(byte[] data, ref int offset)
{
int i;
uint word = 0;
for (i = 0; i < 4; i++)
{
word = (word << 8) | (uint)(data[offset] & 0xff);
offset = (offset + 1) % data.Length;
}
return word;
}
/// <summary>Initializes the Blowfish key schedule.</summary>
private void InitializeKey()
{
_P = new uint[_POrig.Length];
_S = new uint[_SOrig.Length];
Array.Copy(_POrig, _P, _POrig.Length);
Array.Copy(_SOrig, _S, _SOrig.Length);
}
/// <summary>Key the Blowfish cipher.</summary>
/// <param name="keyBytes">The key byte array.</param>
private void Key(byte[] keyBytes)
{
int i;
int koffp = 0;
uint[] lr = { 0, 0 };
int plen = _P.Length, slen = _S.Length;
for (i = 0; i < plen; i++)
_P[i] = _P[i] ^ StreamToWord(keyBytes, ref koffp);
for (i = 0; i < plen; i += 2)
{
Encipher(lr, 0);
_P[i] = lr[0];
_P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2)
{
Encipher(lr, 0);
_S[i] = lr[0];
_S[i + 1] = lr[1];
}
}
/// <summary>
/// Perform the "enhanced key schedule" step described by Provos and Mazieres in "A Future-
/// Adaptable Password Scheme" http://www.openbsd.org/papers/bcrypt-paper.ps.
/// </summary>
/// <param name="saltBytes"> Salt byte array.</param>
/// <param name="inputBytes">Input byte array.</param>
private void EKSKey(byte[] saltBytes, byte[] inputBytes)
{
int i;
int passwordOffset = 0,
saltOffset = 0;
uint[] lr = { 0, 0 };
int plen = _P.Length, slen = _S.Length;
for (i = 0; i < plen; i++)
_P[i] = _P[i] ^ StreamToWord(inputBytes, ref passwordOffset);
for (i = 0; i < plen; i += 2)
{
lr[0] ^= StreamToWord(saltBytes, ref saltOffset);
lr[1] ^= StreamToWord(saltBytes, ref saltOffset);
Encipher(lr, 0);
_P[i] = lr[0];
_P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2)
{
lr[0] ^= StreamToWord(saltBytes, ref saltOffset);
lr[1] ^= StreamToWord(saltBytes, ref saltOffset);
Encipher(lr, 0);
_S[i] = lr[0];
_S[i + 1] = lr[1];
}
}
/// <summary>Perform the central hashing step in the bcrypt scheme.</summary>
/// <exception cref="ArgumentException">Thrown when one or more arguments have unsupported or
/// illegal values.</exception>
/// <param name="inputBytes">The input byte array to hash.</param>
/// <param name="saltBytes"> The salt byte array to hash with.</param>
/// <param name="logRounds"> The binary logarithm of the number of rounds of hashing to apply.</param>
/// <returns>A byte array containing the hashed result.</returns>
private byte[] CryptRaw(byte[] inputBytes, byte[] saltBytes, int logRounds)
{
uint[] cdata = new uint[_BfCryptCiphertext.Length];
Array.Copy(_BfCryptCiphertext, cdata, _BfCryptCiphertext.Length);
int clen = cdata.Length;
if (logRounds < 4 || logRounds > 31)
throw new ArgumentException("Bad number of rounds", "logRounds");
if (saltBytes.Length != BCRYPT_SALT_LEN)
throw new ArgumentException("Bad salt Length", "saltBytes");
uint rounds = 1u << logRounds;
Debug.Assert(rounds > 0, "Rounds must be > 0"); // We overflowed rounds at 31 - added safety check
InitializeKey();
EKSKey(saltBytes, inputBytes);
for (int i = 0; i < rounds; i++)
{
Key(inputBytes);
Key(saltBytes);
}
for (int i = 0; i < 64; i++)
{
for (int j = 0; j < (clen >> 1); j++)
Encipher(cdata, j << 1);
}
byte[] ret = new byte[clen * 4];
for (int i = 0, j = 0; i < clen; i++)
{
ret[j++] = (byte)((cdata[i] >> 24) & 0xff);
ret[j++] = (byte)((cdata[i] >> 16) & 0xff);
ret[j++] = (byte)((cdata[i] >> 8) & 0xff);
ret[j++] = (byte)(cdata[i] & 0xff);
}
return ret;
}
/**
* Compatibility with new OpenBSD function.
* Ported from SSHJ library (https://github.com/hierynomus/sshj)
*/
public void Hash(byte[] hpass, byte[] hsalt, byte[] output)
{
InitializeKey();
EKSKey(hsalt, hpass);
for (int i = 0; i < 64; i++)
{
Key(hsalt);
Key(hpass);
}
uint[] buf = new uint[_OpenBsdIv.Length];
Array.Copy(_OpenBsdIv, 0, buf, 0, _OpenBsdIv.Length);
for (int i = 0; i < 8; i += 2)
{
for (int j = 0; j < 64; j++)
{
Encipher(buf, i);
}
}
for (int i = 0, j = 0; i < buf.Length; i++)
{
// Output of this is little endian
output[j++] = (byte)(buf[i] & 0xff);
output[j++] = (byte)((buf[i] >> 8) & 0xff);
output[j++] = (byte)((buf[i] >> 16) & 0xff);
output[j++] = (byte)((buf[i] >> 24) & 0xff);
}
}
/// <summary>
/// Applies the Bcrypt kdf to derive a key and iv from the passphrase,
/// the key/iv are returned in the output variable.
/// Ported from the SSHJ library. https://github.com/hierynomus/sshj
/// </summary>
/// <param name="password"></param>
/// <param name="salt"></param>
/// <param name="rounds"></param>
/// <param name="output"></param>
public void Pbkdf(byte[] password, byte[] salt, int rounds, byte[] output)
{
using (var sha512 = CryptoAbstraction.CreateSHA512())
{
int nblocks = (output.Length + 31) / 32;
byte[] hpass = sha512.ComputeHash(password);
byte[] hsalt = new byte[64];
byte[] block_b = new byte[4];
byte[] outBytes = new byte[32];
byte[] tmp = new byte[32];
for (int block = 1; block <= nblocks; block++)
{
// Block count is in big endian
block_b[0] = (byte)((block >> 24) & 0xFF);
block_b[1] = (byte)((block >> 16) & 0xFF);
block_b[2] = (byte)((block >> 8) & 0xFF);
block_b[3] = (byte)(block & 0xFF);
hsalt = sha512.ComputeHash(AppendArrays(salt, block_b));
Hash(hpass, hsalt, outBytes);
Array.Copy(outBytes, 0, tmp, 0, outBytes.Length);
for (int round = 1; round < rounds; round++)
{
hsalt = sha512.ComputeHash(tmp);
Hash(hpass, hsalt, tmp);
for (int i = 0; i < tmp.Length; i++)
{
outBytes[i] ^= tmp[i];
}
}
for (int i = 0; i < outBytes.Length; i++)
{
int idx = i * nblocks + (block - 1);
if (idx < output.Length)
{
output[idx] = outBytes[i];
}
}
}
}
}
/// <summary>
/// Appends multiple byte arrays into one array.
/// </summary>
/// <param name="b1"></param>
/// <param name="others"></param>
/// <returns></returns>
private byte[] AppendArrays(byte[] b1, params byte[][] others)
{
//from https://stackoverflow.com/questions/46534429/how-to-convert-java-messagedigest-with-two-update-statements-to-c-sharp
int n = b1.Length;
foreach (var other in others)
{
n += other.Length;
}
var result = new byte[n];
n = 0;
Array.Copy(b1, 0, result, n, b1.Length);
n += b1.Length;
foreach (var other in others)
{
Array.Copy(other, 0, result, n, other.Length);
n += other.Length;
}
return result;
}
/// <summary>Exception for signalling parse errors. </summary>
public class SaltParseException : Exception
{
/// <summary>Default constructor. </summary>
public SaltParseException()
{
}
/// <summary>Initializes a new instance of <see cref="SaltParseException"/>.</summary>
/// <param name="message">The message.</param>
public SaltParseException(string message)
: base(message)
{
}
/// <summary>Initializes a new instance of <see cref="SaltParseException"/>.</summary>
/// <param name="message"> The message.</param>
/// <param name="innerException">The inner exception.</param>
public SaltParseException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
}
@@ -0,0 +1,93 @@
using System;
using Renci.SshNet.Common;
using Renci.SshNet.Security.Chaos.NaCl;
namespace Renci.SshNet.Security.Cryptography
{
/// <summary>
/// Implements ECDSA digital signature algorithm.
/// </summary>
public class ED25519DigitalSignature : DigitalSignature, IDisposable
{
private readonly ED25519Key _key;
/// <summary>
/// Initializes a new instance of the <see cref="ED25519DigitalSignature" /> class.
/// </summary>
/// <param name="key">The ED25519Key key.</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <c>null</c>.</exception>
public ED25519DigitalSignature(ED25519Key key)
{
if (key == null)
throw new ArgumentNullException("key");
_key = key;
}
/// <summary>
/// Verifies the signature.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="signature">The signature.</param>
/// <returns>
/// <c>true</c> if signature was successfully verified; otherwise <c>false</c>.
/// </returns>
/// <exception cref="InvalidOperationException">Invalid signature.</exception>
public override bool Verify(byte[] input, byte[] signature)
{
return Ed25519.Verify(signature, input, _key.PublicKey);
}
/// <summary>
/// Creates the signature.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>
/// Signed input data.
/// </returns>
/// <exception cref="SshException">Invalid ED25519Key key.</exception>
public override byte[] Sign(byte[] input)
{
return Ed25519.Sign(input, _key.PrivateKey);
}
#region IDisposable Members
private bool _isDisposed;
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_isDisposed)
return;
if (disposing)
{
_isDisposed = true;
}
}
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="ED25519DigitalSignature"/> is reclaimed by garbage collection.
/// </summary>
~ED25519DigitalSignature()
{
Dispose(false);
}
#endregion
}
}
@@ -0,0 +1,154 @@
using System;
using Renci.SshNet.Common;
using Renci.SshNet.Security.Cryptography;
using Renci.SshNet.Security.Chaos.NaCl;
namespace Renci.SshNet.Security
{
/// <summary>
/// Contains ED25519 private and public key
/// </summary>
public class ED25519Key : Key, IDisposable
{
private ED25519DigitalSignature _digitalSignature;
private byte[] publicKey = new byte[Ed25519.PublicKeySizeInBytes];
private byte[] privateKey = new byte[Ed25519.ExpandedPrivateKeySizeInBytes];
/// <summary>
/// Gets the Key String.
/// </summary>
public override string ToString()
{
return "ssh-ed25519";
}
/// <summary>
/// Gets or sets the public.
/// </summary>
/// <value>
/// The public.
/// </value>
public override BigInteger[] Public
{
get
{
return new BigInteger[] { publicKey.ToBigInteger() };
}
set
{
publicKey = value[0].ToByteArray().Reverse().TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
}
}
/// <summary>
/// Gets the length of the key.
/// </summary>
/// <value>
/// The length of the key.
/// </value>
public override int KeyLength
{
get
{
return PublicKey.Length;
}
}
/// <summary>
/// Gets the digital signature.
/// </summary>
protected override DigitalSignature DigitalSignature
{
get
{
if (_digitalSignature == null)
{
_digitalSignature = new ED25519DigitalSignature(this);
}
return _digitalSignature;
}
}
/// <summary>
/// Gets the PublicKey Bytes
/// </summary>
public byte[] PublicKey
{
get
{
return publicKey;
}
}
/// <summary>
/// Gets the PrivateKey Bytes
/// </summary>
public byte[] PrivateKey
{
get
{
return privateKey;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
/// </summary>
public ED25519Key()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
/// </summary>
/// <param name="pk">pk data.</param>
/// <param name="sk">sk data.</param>
public ED25519Key(byte[] pk, byte[] sk)
{
publicKey = pk.TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
var seed = new byte[Ed25519.PrivateKeySeedSizeInBytes];
Buffer.BlockCopy(sk, 0, seed, 0, seed.Length);
Ed25519.KeyPairFromSeed(out publicKey, out privateKey, seed);
}
#region IDisposable Members
private bool _isDisposed;
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_isDisposed)
return;
if (disposing)
{
_isDisposed = true;
}
}
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="DsaKey"/> is reclaimed by garbage collection.
/// </summary>
~ED25519Key()
{
Dispose(false);
}
#endregion
}
}
@@ -92,7 +92,7 @@ namespace Renci.SshNet.Security
for (var i = 0; i < _keys.Count; i++)
{
var key = _keys[i];
keys[i] = key.ToBigInteger();
keys[i] = key.ToBigInteger2();
}
return keys;
}