summary refs log tree commit diff
path: root/crypto
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2018-04-17 11:40:43 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2018-04-17 11:40:43 +0700
commit97f8a8a0975a8ae356f341dd15e97c4661aecfb2 (patch)
tree61928e7cd8276b48363a0ffe378bc8a23e720b49 /crypto
parentChange CCM test to use IV of 12 bytes (from bc-java). (diff)
downloadBouncyCastle.NET-ed25519-97f8a8a0975a8ae356f341dd15e97c4661aecfb2.tar.xz
Updated OpenBsdBCrypt to support version 2y.
Diffstat (limited to 'crypto')
-rw-r--r--crypto/src/crypto/generators/OpenBsdBCrypt.cs49
-rw-r--r--crypto/test/src/crypto/test/OpenBsdBCryptTest.cs55
2 files changed, 91 insertions, 13 deletions
diff --git a/crypto/src/crypto/generators/OpenBsdBCrypt.cs b/crypto/src/crypto/generators/OpenBsdBCrypt.cs
index 85c34d769..49f79f95b 100644
--- a/crypto/src/crypto/generators/OpenBsdBCrypt.cs
+++ b/crypto/src/crypto/generators/OpenBsdBCrypt.cs
@@ -3,6 +3,7 @@ using System.IO;
 using System.Text;
 
 using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
 
 namespace Org.BouncyCastle.Crypto.Generators
 {
@@ -33,10 +34,16 @@ namespace Org.BouncyCastle.Crypto.Generators
          * set up the decoding table.
          */
         private static readonly byte[] DecodingTable = new byte[128];
-        private static readonly string Version = "2a"; // previous version was not UTF-8
+        private static readonly string DefaultVersion = "2y";
+        private static readonly ISet AllowedVersions = new HashSet();
 
         static OpenBsdBCrypt()
         {
+            // Presently just the Bcrypt versions.
+            AllowedVersions.Add("2a");
+            AllowedVersions.Add("2y");
+            AllowedVersions.Add("2b");
+
             for (int i = 0; i < DecodingTable.Length; i++)
             {
                 DecodingTable[i] = (byte)0xff;
@@ -56,16 +63,20 @@ namespace Org.BouncyCastle.Crypto.Generators
          * Creates a 60 character Bcrypt String, including
          * version, cost factor, salt and hash, separated by '$'
          *
+         * @param version  the version, 2y,2b or 2a. (2a is not backwards compatible.)
          * @param cost     the cost factor, treated as an exponent of 2
          * @param salt     a 16 byte salt
          * @param password the password
          * @return a 60 character Bcrypt String
          */
-        private static string CreateBcryptString(byte[] password, byte[] salt, int cost)
+        private static string CreateBcryptString(string version, byte[] password, byte[] salt, int cost)
         {
+            if (!AllowedVersions.Contains(version))
+                throw new ArgumentException("Version " + version + " is not accepted by this implementation.", "version");
+
             StringBuilder sb = new StringBuilder(60);
             sb.Append('$');
-            sb.Append(Version);
+            sb.Append(version);
             sb.Append('$');
             sb.Append(cost < 10 ? ("0" + cost) : cost.ToString());
             sb.Append('$');
@@ -80,7 +91,8 @@ namespace Org.BouncyCastle.Crypto.Generators
 
         /**
          * Creates a 60 character Bcrypt String, including
-         * version, cost factor, salt and hash, separated by '$'
+         * version, cost factor, salt and hash, separated by '$' using version
+         * '2y'.
          *
          * @param cost     the cost factor, treated as an exponent of 2
          * @param salt     a 16 byte salt
@@ -89,6 +101,23 @@ namespace Org.BouncyCastle.Crypto.Generators
          */
         public static string Generate(char[] password, byte[] salt, int cost)
         {
+            return Generate(DefaultVersion, password, salt, cost);
+        }
+
+        /**
+         * Creates a 60 character Bcrypt String, including
+         * version, cost factor, salt and hash, separated by '$'
+         *
+         * @param version  the version, may be 2b, 2y or 2a. (2a is not backwards compatible.)
+         * @param cost     the cost factor, treated as an exponent of 2
+         * @param salt     a 16 byte salt
+         * @param password the password
+         * @return a 60 character Bcrypt String
+         */
+        public static string Generate(string version, char[] password, byte[] salt, int cost)
+        {
+            if (!AllowedVersions.Contains(version))
+                throw new ArgumentException("Version " + version + " is not accepted by this implementation.", "version");
             if (password == null)
                 throw new ArgumentNullException("password");
             if (salt == null)
@@ -109,7 +138,7 @@ namespace Org.BouncyCastle.Crypto.Generators
 
             Array.Clear(psw, 0, psw.Length);
 
-            string rv = CreateBcryptString(tmp, salt, cost);
+            string rv = CreateBcryptString(version, tmp, salt, cost);
 
             Array.Clear(tmp, 0, tmp.Length);
 
@@ -133,8 +162,10 @@ namespace Org.BouncyCastle.Crypto.Generators
                 throw new DataLengthException("Bcrypt String length: " + bcryptString.Length + ", 60 required.");
             if (bcryptString[0] != '$' || bcryptString[3] != '$' || bcryptString[6] != '$')
                 throw new ArgumentException("Invalid Bcrypt String format.", "bcryptString");
-            if (!bcryptString.Substring(1, 2).Equals(Version))
-                throw new ArgumentException("Wrong Bcrypt version, 2a expected.", "bcryptString");
+
+            string version = bcryptString.Substring(1, 2);
+            if (!AllowedVersions.Contains(version))
+                throw new ArgumentException("Bcrypt version '" + version + "' is not supported by this implementation", "bcryptString");
 
             int cost = 0;
             try
@@ -143,7 +174,7 @@ namespace Org.BouncyCastle.Crypto.Generators
             }
             catch (Exception nfe)
             {
-                throw new ArgumentException("Invalid cost factor: " + bcryptString.Substring(4, 2), "bcryptString");
+                throw new ArgumentException("Invalid cost factor: " + bcryptString.Substring(4, 2), "bcryptString", nfe);
             }
             if (cost < 4 || cost > 31)
                 throw new ArgumentException("Invalid cost factor: " + cost + ", 4 < cost < 31 expected.");
@@ -155,7 +186,7 @@ namespace Org.BouncyCastle.Crypto.Generators
             int start = bcryptString.LastIndexOf('$') + 1, end = bcryptString.Length - 31;
             byte[] salt = DecodeSaltString(bcryptString.Substring(start, end - start));
 
-            string newBcryptString = Generate(password, salt, cost);
+            string newBcryptString = Generate(version, password, salt, cost);
 
             return bcryptString.Equals(newBcryptString);
         }
diff --git a/crypto/test/src/crypto/test/OpenBsdBCryptTest.cs b/crypto/test/src/crypto/test/OpenBsdBCryptTest.cs
index 8501588ee..fabe84d6b 100644
--- a/crypto/test/src/crypto/test/OpenBsdBCryptTest.cs
+++ b/crypto/test/src/crypto/test/OpenBsdBCryptTest.cs
@@ -74,6 +74,28 @@ namespace Org.BouncyCastle.Crypto.Tests
             new string[]{"8nv;PAN~-FQ]Emh@.TKG=^.t8R0EQC0T?x9|9g4xzxYmSbBO1qDx8kv-ehh0IBv>3KWhz.Z~jUF0tt8[5U@8;5:=[v6pf.IEJ", "$2a$08$eXo9KDc1BZyybBgMurpcD.GA1/ch3XhgBnIH10Xvjc2ogZaGg3t/m"},
         };
 
+
+        // 2y vectors generated from htpasswd -nB -C 12, nb leading username was removed.
+        private static readonly string[,] twoYVec = new string[,]{
+            {"a", "$2y$12$DB3BUbYa/SsEL7kCOVji0OauTkPkB5Y1OeyfxJHM7jvMrbml5sgD2"},
+            {"abc", "$2y$12$p.xODEbFcXUlHGbNxWZqAe6AA5FWupqXmN9tZea2ACDhwIx4EA2a6"},
+            {"hello world", "$2y$12$wfkxITYXjNLVpEi9nOjz7uXMhCXKSTY7O2y7X4bwY89aGSvRziguq"},
+            {"ABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXY", "$2y$12$QwAt5kuG68nW7v.87q0QPuwdki3romFc/RU/RV3Qqk4FPw6WdbQzu"}
+        };
+
+        // Same as 2y vectors only version changed to 2b to verify handling of that version.
+        private static readonly string[,] twoBVec = new string[,]{
+            {"a", "$2b$12$DB3BUbYa/SsEL7kCOVji0OauTkPkB5Y1OeyfxJHM7jvMrbml5sgD2"},
+            {"abc", "$2b$12$p.xODEbFcXUlHGbNxWZqAe6AA5FWupqXmN9tZea2ACDhwIx4EA2a6"},
+            {"hello world", "$2b$12$wfkxITYXjNLVpEi9nOjz7uXMhCXKSTY7O2y7X4bwY89aGSvRziguq"},
+            {"ABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXY", "$2b$12$QwAt5kuG68nW7v.87q0QPuwdki3romFc/RU/RV3Qqk4FPw6WdbQzu"}
+        };
+
+        public static void Main(string[] args)
+        {
+            RunTest(new OpenBsdBCryptTest());
+        }
+
         public override string Name
         {
             get { return "OpenBsdBCrypt"; }
@@ -129,11 +151,36 @@ namespace Org.BouncyCastle.Crypto.Tests
                     Fail("test4 mismatch: " + "[" + i + "] " + password);
                 }
             }
-        }
 
-        public static void Main(string[] args)
-        {
-            RunTest(new OpenBsdBCryptTest());
+            {
+                int lower = twoYVec.GetLowerBound(0);
+                int upper = twoYVec.GetUpperBound(0);
+                for (int i = lower; i <= upper; i++)
+                {
+                    password = twoYVec[i, 0];
+                    encoded = twoYVec[i, 1];
+
+                    if (!OpenBsdBCrypt.CheckPassword(encoded, password.ToCharArray()))
+                    {
+                        Fail("twoYVec mismatch: " + "[" + i + "] " + password);
+                    }
+                }
+            }
+
+            {
+                int lower = twoBVec.GetLowerBound(0);
+                int upper = twoBVec.GetUpperBound(0);
+                for (int i = lower; i <= upper; i++)
+                {
+                    password = twoBVec[i, 0];
+                    encoded = twoBVec[i, 1];
+
+                    if (!OpenBsdBCrypt.CheckPassword(encoded, password.ToCharArray()))
+                    {
+                        Fail("twoBVec mismatch: " + "[" + i + "] " + password);
+                    }
+                }
+            }
         }
 
         [Test]