1db9287 Add test scripts.
test/BcryptTest.cfc (new) | 104 +++++++++++++++
test/Pbkdf2Test.cfc (new) | 54 ++++++++
test/ScryptTest.cfc (new) | 66 ++++++++++
test/TestBase.cfc (new) | 115 +++++++++++++++++
test/UsageTest.cfc (new) | 135 ++++++++++++++++++++
test/runtests.cfm (new) | 17 +++
6 files changed, 491 insertions(+)
diff --git a/test/BcryptTest.cfc b/test/BcryptTest.cfc
new file mode 100644
index 0000000..7bbc126
--- /dev/null
+++ b/test/BcryptTest.cfc
@@ -0,0 +1,104 @@
+<cfcomponent extends="TestBase">
+<cfscript>
+
+ function init( IncludeSlowTests )
+ {
+ super.init( argumentcollection=arguments );
+
+ this.Name = 'bcrypt';
+
+ var TestData =
+ [ [ "", "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." ]
+ , [ "", "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" ]
+ , [ "", "$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" ]
+ , [ "", "$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" ]
+ , [ "a", "$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" ]
+ , [ "a", "$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." ]
+ , [ "a", "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" ]
+ , [ "a", "$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" ]
+ , [ "abc", "$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" ]
+ , [ "abc", "$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" ]
+ , [ "abc", "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" ]
+ , [ "abc", "$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" ]
+ , [ "abcdefghijklmnopqrstuvwxyz", "$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" ]
+ , [ "abcdefghijklmnopqrstuvwxyz", "$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." ]
+ , [ "abcdefghijklmnopqrstuvwxyz", "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" ]
+ , [ "abcdefghijklmnopqrstuvwxyz", "$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" ]
+ , [ "~!@##$%^&*() ~!@##$%^&*()PNBFRD", "$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" ]
+ , [ "~!@##$%^&*() ~!@##$%^&*()PNBFRD", "$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" ]
+ , [ "~!@##$%^&*() ~!@##$%^&*()PNBFRD", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" ]
+ , [ "~!@##$%^&*() ~!@##$%^&*()PNBFRD", "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" ]
+ ];
+
+ super.start();
+
+ this.test_info( TestData );
+
+ if ( IncludeSlowTests )
+ this.test_basic( TestData );
+ else
+ skip( ArrayLen(TestData)*2 );
+
+ this.test_nonascii();
+
+ return super.end();
+ }
+
+
+ function test_info( TestData )
+ {
+ var info1 = PassphraseInfo('$2a$10$9zXu55aPNya8ek17DwCXZ.X6kExa5cK5bpGmyTBrqD1dg76rkWz4y');
+ assertEqual(info1.Algorithm,'BCrypt');
+ assertEqual(info1.Status,'Supported');
+ assertEqual(info1.Version,'2a');
+ assertEqual(info1.Rounds,10);
+ assertEqual(info1.Salt,'9zXu55aPNya8ek17');
+ assertEqual(info1.Hash,'DwCXZ.X6kExa5cK5bpGmyTBrqD1dg76rkWz4y');
+ assertEqual(StructCount(info1),6);
+
+ var info = PassphraseInfo(Arguments.TestData[1][2]);
+ assertEqual(info.Algorithm,'BCrypt');
+ assertEqual(info.Status,'Supported');
+ assertEqual(info.Version,'2a');
+ assertEqual(info.Rounds,6);
+ assertEqual(info.Salt,'DCq7YPn5Rq63x1La');
+ assertEqual(info.Hash,'d4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.');
+
+ info = PassphraseInfo(Arguments.TestData[20][2]);
+ assertEqual(info.Algorithm,'BCrypt');
+ assertEqual(info.Status,'Supported');
+ assertEqual(info.Version,'2a');
+ assertEqual(info.Rounds,12);
+ assertEqual(info.Salt,'WApznUOJfkEGSmYR');
+ assertEqual(info.Hash,'fnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC');
+ }
+
+
+ function test_basic( TestData )
+ {
+ for ( var i=1 ; i<=ArrayLen(Arguments.TestData) ; ++i )
+ {
+ assertTrue( PassphraseCheck(Arguments.TestData[i][1],Arguments.TestData[i][2]) );
+ assertFalse( PassphraseCheck(Arguments.TestData[i][1],Arguments.TestData[((i + 4) % ArrayLen(Arguments.TestData))+1][2]) );
+ }
+ }
+
+
+ function test_nonascii()
+ {
+ var TestData =
+ [ RepeatString(chr(2605),8)
+ , "????????"
+ ];
+
+ var h1 = PassphraseHash(TestData[1],'bcrypt',{rounds:6});
+ var h2 = PassphraseHash(TestData[2],'bcrypt',{rounds:6});
+ assertTrue(PassphraseCheck(TestData[1],h1));
+ assertFalse(PassphraseCheck(TestData[1],h2));
+ assertTrue(PassphraseCheck(TestData[2],h2));
+ assertFalse(PassphraseCheck(TestData[2],h1));
+ }
+
+
+</cfscript>
+</cfcomponent>
\ No newline at end of file
diff --git a/test/Pbkdf2Test.cfc b/test/Pbkdf2Test.cfc
new file mode 100644
index 0000000..c8f9462
--- /dev/null
+++ b/test/Pbkdf2Test.cfc
@@ -0,0 +1,54 @@
+<cfcomponent extends="TestBase">
+<cfscript>
+
+ function init( IncludeSlowTests )
+ {
+ super.init( argumentcollection=arguments );
+
+ this.Name = 'pbkdf2';
+
+ super.start();
+
+ this.test_info();
+
+ if ( IncludeSlowTests )
+ this.test_basic();
+ else
+ skip( 30 );
+
+ return super.end();
+ }
+
+
+ function test_info( TestData )
+ {
+ var info2 = PassphraseInfo('180:5fabd8b160f5f9225ec5569ce2f02d5a2e29a29e0b280614:d11a602ecc7830280b25cdd29b539e9e0f8438a0a43e6637');
+ assertEqual(info2.Algorithm,'PBKDF2');
+ assertEqual(info2.Status,'Supported');
+ assertEqual(info2.Iterations,180);
+ assertEqual(info2.Salt,'5fabd8b160f5f9225ec5569ce2f02d5a2e29a29e0b280614');
+ assertEqual(info2.Hash,'d11a602ecc7830280b25cdd29b539e9e0f8438a0a43e6637');
+ assertEqual(StructCount(info2),5);
+ }
+
+
+ function test_basic()
+ {
+ for ( var i=0 ; i<10 ; ++i )
+ {
+ var password = ""&i;
+ var hash = PassphraseHash(password,'pbkdf2');
+ var secondHash = PassphraseHash(password,'pbkdf2');
+
+ assertNotEqual( hash , secondHash , "Two hashes are equal." );
+
+ var wrongPassword = ""&(i+1);
+ assertFalse( PassphraseCheck(wrongPassword,hash) , "Wrong password accepted." );
+
+ assertTrue( PassphraseCheck(password,hash) , "Good password not accepted." );
+ }
+ }
+
+
+</cfscript>
+</cfcomponent>
\ No newline at end of file
diff --git a/test/ScryptTest.cfc b/test/ScryptTest.cfc
new file mode 100644
index 0000000..af0cb6e
--- /dev/null
+++ b/test/ScryptTest.cfc
@@ -0,0 +1,66 @@
+<cfcomponent extends="TestBase">
+<cfscript>
+
+ function init( IncludeSlowTests )
+ {
+ super.init( argumentcollection=arguments );
+
+ this.Name = 'scrypt';
+
+ super.start();
+
+ this.test_info();
+
+ if ( IncludeSlowTests )
+ this.test_basic();
+ else
+ skip( 30 );
+
+ return super.end();
+ }
+
+
+ function test_info( TestData )
+ {
+ var info3 = PassphraseInfo('$s0$501ff$5JQALASFiKGKdfY9Z0GYMA==$8c4aMGktdRlLIeY+erIA62fNtgb2OxJrjyhw+XeWHk4=');
+ assertEqual(info3.Algorithm,'SCrypt');
+ assertEqual(info3.Status,'Supported');
+ assertEqual(info3.Version,'0');
+ assertEqual(info3.CpuCost,32);
+ assertEqual(info3.MemoryCost,1);
+ assertEqual(info3.Parallelization,255);
+ assertEqual(info3.Salt,'5JQALASFiKGKdfY9Z0GYMA==');
+ assertEqual(info3.Hash,'8c4aMGktdRlLIeY+erIA62fNtgb2OxJrjyhw+XeWHk4=');
+ assertEqual(StructCount(info3),8);
+ }
+
+
+ function test_basic()
+ {
+ var Passwd = "secret";
+ var N = 16384;
+ var r = 8;
+ var p = 1;
+
+ var hashed = PassphraseHash(Passwd,'scrypt',{CpuCost:N,MemoryCost:r,Parallelization:p});
+
+ var parts = hashed.split('\$');
+
+ assertEqual(len(parts),5);
+ assertEqual(parts[1],"");
+ assertEqual(parts[2],"s0");
+ assertEqual(len(BinaryDecode(parts[4],'base64')),16);
+ assertEqual(len(BinaryDecode(parts[5],'base64')),32);
+
+ var params = InputBaseN(parts[3],16);
+ assertEqual( N , 2^( BitAnd(BitShRn(params,16),65535)) );
+ assertEqual( r , BitAnd(BitShRn(params,8),255) );
+ assertEqual( p , BitAnd(BitShRn(params,0),255) );
+
+ assertTrue(PassphraseCheck(passwd,hashed));
+ assertFalse(PassphraseCheck("s3cr3t",hashed));
+ }
+
+
+</cfscript>
+</cfcomponent>
\ No newline at end of file
diff --git a/test/TestBase.cfc b/test/TestBase.cfc
new file mode 100644
index 0000000..26d3079
--- /dev/null
+++ b/test/TestBase.cfc
@@ -0,0 +1,115 @@
+<cfcomponent>
+<cfscript>
+
+ function init()
+ {
+ this.Results = [];
+ this.Messages = [];
+
+ variables.Status =
+ { Pass : "."
+ , Fail : "!"
+ , Skip : "-"
+ };
+ }
+
+
+ function start()
+ {
+ this.StartTime = getTickCount();
+ }
+
+
+ function end()
+ {
+ return '<br/>#this.Name#: #ArrayToList(this.Results,'')#'
+ & '<br/>time: #getTickCount()-this.StartTime#ms'
+ & '<pre>#ArrayToList(this.Messages,'<br/>')#</pre>'
+ ;
+ }
+
+
+ function assertEqual(a,b,Msg='')
+ {
+ if ( len(Arguments.Msg) )
+ return assertTrue( a eq b , Arguments.Msg );
+
+ return assertTrue( a eq b , 'Expected [#a#] to be same as [#b#]' );
+ }
+
+
+ function assertNotEqual(a,b,Msg='')
+ {
+ if ( len(Arguments.Msg) )
+ return assertFalse( a eq b , Arguments.Msg );
+
+ return assertFalse( a eq b , 'Expected [#a#] to differ from [#b#]' );
+ }
+
+
+ function assertTrue(a,Msg='')
+ {
+ if ( not isBoolean(a) )
+ return fail('Expected boolean, received [#a#]');
+
+ if ( not a )
+ {
+ if ( len(Arguments.Msg) )
+ return fail(Arguments.Msg);
+
+ return fail('Expected True received [#a#]');
+ }
+
+ return pass();
+ }
+
+
+ function assertFalse(a,Msg='')
+ {
+ if ( not isBoolean(a) )
+ return fail('Expected boolean, received [#a#]');
+
+ if ( a )
+ {
+ if ( len(Arguments.Msg) )
+ return fail(Arguments.Msg);
+
+ return fail('Expected False received [#a#]');
+ }
+
+ return pass();
+ }
+
+
+ function pass()
+ {
+ ArrayAppend(this.Results,Status.Pass);
+ return true;
+ }
+
+
+ function fail(Msg)
+ {
+ if ( len(Arguments.Msg) )
+ logMessage(Msg);
+
+ ArrayAppend(this.Results,Status.Fail);
+ return false;
+ }
+
+
+ function skip(n=1)
+ {
+ ArrayAppend(this.Results,RepeatString(Status.Skip,n));
+ logMessage('Skipped #n# assertions.');
+ }
+
+
+ function logMessage(Msg)
+ {
+ ArrayAppend(this.Messages,Msg);
+ }
+
+
+</cfscript>
+</cfcomponent>
\ No newline at end of file
diff --git a/test/UsageTest.cfc b/test/UsageTest.cfc
new file mode 100644
index 0000000..cf40e83
--- /dev/null
+++ b/test/UsageTest.cfc
@@ -0,0 +1,135 @@
+<cfcomponent extends="TestBase">
+<cfscript>
+
+ function init()
+ {
+ super.init( argumentcollection=arguments );
+
+ this.Name = 'usage';
+
+ super.start();
+
+ this.test_info();
+ this.test_hash();
+ this.test_check();
+
+ return super.end();
+ }
+
+
+ function test_info()
+ {
+ try
+ {
+ var info = PassphraseInfo('$8$unknown-hash');
+ assertFalse(1,"Expected exception to be thrown");
+ }
+ catch( any e )
+ {
+ assertEqual(e.Message,"Unknown Algorithm Signature");
+ }
+
+ var info = PassphraseInfo('$2a$10$9zXu55aPNya8ek17DwCXZ.X6kExa5cK5bpGmyTBrqD1dg76rkWz4y');
+ assertEqual(info.Algorithm,'BCrypt');
+ assertEqual(info.Status,'Supported');
+
+ var info = PassphraseInfo('180:5fabd8b160f5f9225ec5569ce2f02d5a2e29a29e0b280614:d11a602ecc7830280b25cdd29b539e9e0f8438a0a43e6637');
+ assertEqual(info.Algorithm,'PBKDF2');
+ assertEqual(info.Status,'Supported');
+
+ var info = PassphraseInfo('$s0$501ff$5JQALASFiKGKdfY9Z0GYMA==$8c4aMGktdRlLIeY+erIA62fNtgb2OxJrjyhw+XeWHk4=');
+ assertEqual(info.Algorithm,'SCrypt');
+ assertEqual(info.Status,'Supported');
+
+ var info = PassphraseInfo('$1$etNnh7FA$OlM7eljE/B7F1J4XYNnk81');
+ assertEqual(info.Algorithm,'md5crypt');
+ assertEqual(info.Status,'Obsolete');
+ assertEqual(info.Salt,'etNnh7FA');
+ assertEqual(info.Hash,'OlM7eljE/B7F1J4XYNnk81');
+
+ var info = PassphraseInfo('$3$$8846f7eaee8fb117ad06bdd830b7586c');
+ assertEqual(info.Algorithm,'NT-Hash');
+ assertEqual(info.Status,'Obsolete');
+ assertEqual(info.Hash,'8846f7eaee8fb117ad06bdd830b7586c');
+
+ var info = PassphraseInfo('$5$9ks3nNEqv31FX.F$gdEoLFsCRsn/WRN3wxUnzfeZLoooVlzeF4WjLomTRFD');
+ assertEqual(info.Algorithm,'SHA-2');
+ assertEqual(info.Version,'256');
+ assertEqual(info.Status,'Unsupported');
+ assertEqual(info.Rounds,'5000');
+ assertEqual(info.Salt,'9ks3nNEqv31FX.F');
+ assertEqual(info.Hash,'gdEoLFsCRsn/WRN3wxUnzfeZLoooVlzeF4WjLomTRFD');
+
+ var info = PassphraseInfo('$6$qoE2letU$wWPRl.PVczjzeMVgjiA8LLy2nOyZbf7Amj3qLIL978o18gbMySdKZ7uepq9tmMQXxyTIrS12Pln.2Q/6Xscao0');
+ assertEqual(info.Algorithm,'SHA-2');
+ assertEqual(info.Version,'512');
+ assertEqual(info.Status,'Unsupported');
+ assertEqual(info.Rounds,'5000');
+ assertEqual(info.Salt,'qoE2letU');
+ assertEqual(info.Hash,'wWPRl.PVczjzeMVgjiA8LLy2nOyZbf7Amj3qLIL978o18gbMySdKZ7uepq9tmMQXxyTIrS12Pln.2Q/6Xscao0');
+
+ var info = PassphraseInfo('$md5,rounds=5000$GUBv0xjJ$mSwgIswdjlTY0YxV7HBVm0');
+ assertEqual(info.Algorithm,'SunMD5');
+ assertEqual(info.Status,'Obsolete');
+ assertEqual(info.Rounds,'5000');
+ assertEqual(info.Salt,'GUBv0xjJ');
+ assertEqual(info.Hash,'mSwgIswdjlTY0YxV7HBVm0');
+
+ }
+
+
+ function test_hash()
+ {
+ var r = RandRange(4,6);
+ var hash = PassphraseHash('x','bcrypt',{rounds:r});
+ var info = PassphraseInfo(hash);
+ assertEqual(info.Algorithm,'BCrypt');
+ assertEqual(info.Rounds,r);
+ assertNotEqual(hash,PassphraseHash('x','bcrypt',{rounds:r}));
+
+ var i = RandRange(1000,2000);
+ var hash = PassphraseHash('x','pbkdf2',{iterations:i});
+ var info = PassphraseInfo(hash);
+ assertEqual(info.Algorithm,'PBKDF2');
+ assertEqual(info.Iterations,i);
+ assertNotEqual(hash,PassphraseHash('x','pbkdf2',{iterations:i}));
+
+ var c = 2^RandRange(1,3);
+ var m = RandRange(1,3);
+ var hash = PassphraseHash('x','scrypt',{CpuCost:c,MemoryCost:m});
+ var info = PassphraseInfo(hash);
+ assertEqual(info.Algorithm,'SCrypt');
+ assertEqual(info.CpuCost,c);
+ assertEqual(info.MemoryCost,m);
+ assertNotEqual(hash,PassphraseHash('x','scrypt',{CpuCost:c,MemoryCost:m}));
+ }
+
+
+ function test_check()
+ {
+ var hash = PassphraseHash('x','bcrypt',{rounds:4});
+ assertTrue(PassphraseCheck('x',hash));
+ assertFalse(PassphraseCheck('x ',hash));
+
+ var hash = PassphraseHash('x','pbkdf2',{iterations:1000});
+ assertTrue(PassphraseCheck('x',hash));
+ assertFalse(PassphraseCheck('x ',hash));
+
+ var hash = PassphraseHash('x','scrypt',{CpuCost:2,MemoryCost:2});
+ assertTrue(PassphraseCheck('x',hash));
+ assertFalse(PassphraseCheck('x ',hash));
+
+ try
+ {
+ PassphraseCheck(hash,'');
+ assertFalse(1,"Expected exception to be thrown");
+ }
+ catch( any e )
+ {
+ assertEqual(e.Message,"Unknown Algorithm Signature");
+ }
+ }
+
+
+</cfscript>
+</cfcomponent>
\ No newline at end of file
diff --git a/test/runtests.cfm b/test/runtests.cfm
new file mode 100644
index 0000000..681c6fe
--- /dev/null
+++ b/test/runtests.cfm
@@ -0,0 +1,17 @@
+<cfsetting showdebugoutput=false enablecfoutputonly=true />
+<cfcontent type="text/html"><cfoutput><!doctype html>
+ <meta charset=utf-8 />
+ <style>html{font-family:monospace;}</style>
+ <title>cfPassphrase Tests</title>
+ <b>Testing...</b>
+
+ <cfset IncludeSlowTests = StructKeyExists(Url,'RunAll') />
+
+ <cfset s = getTickCount() />
+ <li>#createObject('UsageTest').init( IncludeSlowTests )#
+ <li>#createObject('BcryptTest').init( IncludeSlowTests )#
+ <li>#createObject('Pbkdf2Test').init( IncludeSlowTests )#
+ <li>#createObject('ScryptTest').init( IncludeSlowTests )#
+ <p><b>Total Time: ~#round((getTickCount()-s)/1000)# seconds</b>
+
+</html></cfoutput>
\ No newline at end of file