Sorcerer's IsleCode cfPassphrase / diff

04bb783 Add initial code.

 readme.md                                                            |  12 ++
 src/sorcerersisle/cfpassphrase/Impl.java (new)                       | 158 ++++++++++++++++++++
 src/sorcerersisle/cfpassphrase/Utils.java (new)                      |  60 ++++++++
 src/sorcerersisle/cfpassphrase/cfPassphrase.java (new)               |  41 +++++
 src/sorcerersisle/cfpassphrase/coldfusion/Passphrase.cfc (new)       |  77 ++++++++++
 src/sorcerersisle/cfpassphrase/coldfusion/Passphrase.cfm (new)       | 106 +++++++++++++
 src/sorcerersisle/cfpassphrase/coldfusion/PassphraseCheck.java (new) |  26 ++++
 src/sorcerersisle/cfpassphrase/coldfusion/PassphraseHash.java (new)  |  39 +++++
 src/sorcerersisle/cfpassphrase/coldfusion/PassphraseInfo.java (new)  |  30 ++++
 src/sorcerersisle/cfpassphrase/openbd/PassphraseCheck.java (new)     |  46 ++++++
 src/sorcerersisle/cfpassphrase/openbd/PassphraseHash.java (new)      |  52 +++++++
 src/sorcerersisle/cfpassphrase/openbd/PassphraseInfo.java (new)      |  47 ++++++
 src/sorcerersisle/cfpassphrase/openbd/PassphraseTag.java (new)       |  81 ++++++++++
 src/sorcerersisle/cfpassphrase/openbd/cfPassphrasePlugin.java (new)  |  42 ++++++
 src/sorcerersisle/cfpassphrase/railo/PassphraseCheck.java (new)      |  40 +++++
 src/sorcerersisle/cfpassphrase/railo/PassphraseHash.java (new)       |  56 +++++++
 src/sorcerersisle/cfpassphrase/railo/PassphraseInfo.java (new)       |  43 ++++++
 src/sorcerersisle/cfpassphrase/railo/PassphraseTag.java (new)        |  92 ++++++++++++
 src/sorcerersisle/cfpassphrase/railo/cfPassphrase.fld (new)          |  40 +++++
 src/sorcerersisle/cfpassphrase/railo/cfPassphrase.tld (new)          |  61 ++++++++
 20 files changed, 1149 insertions(+)
diff --git a/readme.md b/readme.md
index 80769fa..614a28a 100644
--- a/readme.md
+++ b/readme.md
@@ -26,6 +26,18 @@ REQUIREMENTS
 
 cfPassphrase is intended to run on any CFML engine.
 
+At present, it has been tested with:
+
+* ColdFusion 10 (10,0,0,282462)
+* ColdFusion 9  (9,0,1,274733)
+* OpenBD 3.1    (nightly 2013-03-12)
+* Railo 4.0     (4.0.2.002)
+
+It may or not work with other versions.
+
+Please log any issues found:
+https://github.com/boughtonp/cfpassphrase/issues
+
 
 LICENSING
 ---------
diff --git a/src/sorcerersisle/cfpassphrase/Impl.java b/src/sorcerersisle/cfpassphrase/Impl.java
new file mode 100644
index 0000000..c4fec89
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/Impl.java
@@ -0,0 +1,158 @@
+package sorcerersisle.cfpassphrase;
+
+import mindrot.jbcrypt.BCrypt;
+import crackstation.PBKDF2.PasswordHash;
+import com.lambdaworks.crypto.SCryptUtil;
+import java.util.Map;
+import java.util.HashMap;
+
+
+public final class Impl
+{
+
+
+	public static String hash
+		( String             Passphrase
+		, String             Algorithm
+		, Map<String,Object> AlgorithmParams
+		)
+	throws Exception
+	{
+
+		switch ( Utils.Algorithm.fromString(Algorithm) )
+		{
+
+			case bcrypt:
+				Integer Rounds = Utils.StructGetInt(AlgorithmParams,"Rounds",16);
+
+				String Salt = ( Rounds == null )
+					? BCrypt.gensalt()
+					: BCrypt.gensalt(Rounds)
+					;
+
+				return BCrypt.hashpw
+					( Passphrase
+					, Salt
+					);
+
+			case pbkdf2:
+				return PasswordHash.createHash
+					( Passphrase
+					, Utils.StructGetInt(AlgorithmParams,"Iterations",86000)
+					, Utils.StructGetInt(AlgorithmParams,"SaltBytes",24)
+					, Utils.StructGetInt(AlgorithmParams,"HashBytes",24)
+					);
+
+			case scrypt:
+				return SCryptUtil.scrypt
+					( Passphrase
+					, Utils.StructGetInt(AlgorithmParams,"CpuCost",2^16)
+					, Utils.StructGetInt(AlgorithmParams,"MemoryCost",8)
+					, Utils.StructGetInt(AlgorithmParams,"Parallelization",1)
+					);
+
+			default:
+				throw new Exception("Unsupported Algorithm");
+
+		}
+	}
+
+
+	public static Boolean check
+		( String Passphrase
+		, String Hash
+		, String Algorithm
+		)
+	throws Exception
+	{
+		switch
+			( Algorithm == null
+			? Utils.identifyAlgorithm(Hash)
+			: Utils.Algorithm.fromString(Algorithm)
+			)
+		{
+
+			case bcrypt:
+				return BCrypt.checkpw
+					( Passphrase
+					, Hash
+					);
+
+			case pbkdf2:
+				return PasswordHash.validatePassword
+					( Passphrase
+					, Hash
+					);
+
+			case scrypt:
+				return SCryptUtil.check
+					( Passphrase
+					, Hash
+					);
+
+			default:
+				throw new Exception("Unsupported Algorithm");
+
+		}
+	}
+
+
+	public static Map<String,String> info
+		( String Hash
+		, String Algorithm
+		)
+	throws Exception
+	{
+		String[]           Parts;
+		Map<String,String> Info = new HashMap<String,String>();
+
+		switch
+			( Algorithm == null
+			? Utils.identifyAlgorithm(Hash)
+			: Utils.Algorithm.fromString(Algorithm)
+			)
+		{
+
+			case bcrypt:
+				Parts = Hash.substring(1).split("\\$");
+
+				Info.put("Algorithm"  , "bcrypt" );
+				Info.put("Version"    , Parts[0] );
+				Info.put("Rounds"     , Parts[1] );
+				Info.put("Salt"       , Parts[2].substring(0,16) );
+				Info.put("Hash"       , Parts[2].substring(16)   );
+
+				return Info;
+
+			case pbkdf2:
+				Parts = Hash.split(":");
+
+				Info.put("Algorithm"  , "pbkdf2" );
+				Info.put("Iterations" , Parts[0] );
+				Info.put("Salt"       , Parts[1] );
+				Info.put("Hash"       , Parts[2] );
+
+				return Info;
+
+			case scrypt:
+				Parts = Hash.substring(2).split("\\$");
+				long Params = Long.parseLong(Parts[1],16);
+
+				Info.put("Algorithm"       , "scrypt" );
+				Info.put("Version"         , Parts[0] );
+				Info.put("CpuCost"         , String.valueOf(Math.round((Math.pow(2, Params >> 16 & 0xffff)))) );
+				Info.put("MemoryCost"      , String.valueOf(Params >> 8 & 0xff) );
+				Info.put("Parallelization" , String.valueOf(Params      & 0xff) );
+				Info.put("Salt"            , Parts[2] );
+				Info.put("Hash"            , Parts[3] );
+
+				return Info;
+
+			default:
+				throw new Exception("Unsupported Algorithm");
+
+		}
+	}
+
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/Utils.java b/src/sorcerersisle/cfpassphrase/Utils.java
new file mode 100644
index 0000000..0a4cf47
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/Utils.java
@@ -0,0 +1,60 @@
+package sorcerersisle.cfpassphrase;
+
+import java.util.Map;
+
+public final class Utils
+{
+
+	public static enum Algorithm
+	{ bcrypt , pbkdf2 , scrypt
+	; public static Algorithm fromString(String Str)
+		{
+			if ( Str == null ) return DefaultAlgorithm;
+			try { return valueOf(Str.toLowerCase());}
+			catch (Exception ex){return null;}
+		}
+	}
+
+	static Algorithm DefaultAlgorithm = Algorithm.bcrypt;
+
+
+	static Algorithm identifyAlgorithm
+		( String Hash )
+	throws Exception
+	{
+		if ( Hash.matches("^\\$2a?\\$\\d+\\$[0-9A-Za-z./]+$") )
+			return Algorithm.bcrypt;
+
+		else if ( Hash.matches("^\\d+:[0-9a-f]+:[0-9a-f]+$") )
+			return Algorithm.pbkdf2;
+
+		else if ( Hash.matches("^\\$s0\\$[0-9a-z]+(?:\\$[0-9A-Za-z+=/]+){2}$") )
+			return Algorithm.scrypt;
+
+		else
+			throw new Exception("Unknown Algorithm Signature");
+	}
+
+
+	static Integer StructGetInt
+		( Map<String,Object> Struct
+		, String             KeyName
+		, Integer            Default
+		)
+	throws Exception
+	{
+		if ( Struct == null || ! Struct.containsKey(KeyName) )
+			return Default;
+
+		Object Value = Struct.get(KeyName);
+
+		if ( Value instanceof Integer )
+			return (Integer) Value;
+		else if ( Value instanceof Double )
+			return (int) Math.round( (Double)Value );
+		else
+			return Integer.valueOf( (String)Value );
+	}
+
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/cfPassphrase.java b/src/sorcerersisle/cfpassphrase/cfPassphrase.java
new file mode 100644
index 0000000..93fc527
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/cfPassphrase.java
@@ -0,0 +1,41 @@
+package sorcerersisle.cfpassphrase;
+
+import java.io.IOException;
+import javax.swing.JOptionPane;
+
+
+public final class cfPassphrase
+{
+
+	public static void main
+		( String[] Args )
+	{
+		String Msg =
+			  "cfPassphrase v0.0.000\n"
+			+ "\n"
+			+ "Project Info: http://sorcerersisle.com/projects:cfpassphrase.html\n"
+			+ "Install Docs: https://github.com/boughtonp/cfpassphrase/wiki/Installation\n"
+			;
+
+
+		if (isJavaw() )
+			JOptionPane.showMessageDialog(null,Msg);
+		else
+			System.out.println(Msg);
+	}
+
+
+	private static boolean isJavaw()
+	{
+		try
+		{
+			System.in.available();
+			return false;
+		}
+		catch (IOException e)
+		{
+			return true;
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/coldfusion/Passphrase.cfc b/src/sorcerersisle/cfpassphrase/coldfusion/Passphrase.cfc
new file mode 100644
index 0000000..d0dbbec
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/coldfusion/Passphrase.cfc
@@ -0,0 +1,77 @@
+<cfcomponent output=false >
+
+
+	<cffunction name="Hash" returntype="String" output=false access="public">
+		<cfargument name="Passphrase"      type="String" required />
+		<cfargument name="Algorithm"       type="String" optional />
+		<cfargument name="AlgorithmParams" type="Struct" optional />
+
+		<cfset var JavaObj = createObject( 'java' , "sorcerersisle.cfpassphrase.coldfusion.PassphraseHash" ) />
+
+		<cfif StructKeyExists(Arguments,'AlgorithmParams')>
+			<cfif NOT StructKeyExists(Arguments,'Algorithm')>
+				<cfthrow
+					type    = "cfPassphrase.ColdFusion.Cfc.MissingArgument.Algorithm"
+					message = "You must specify the [Algorithm] argument when [AlgorithmParams] is specified."
+				/>
+			</cfif>
+			<cfreturn JavaObj.call
+				( Arguments.Passphrase
+				, Arguments.Algorithm
+				, Arguments.AlgorithmParams
+				)/>
+		<cfelseif StructKeyExists(Arguments,'Algorithm')>
+			<cfreturn JavaObj.call
+				( Arguments.Passphrase
+				, Arguments.Algorithm
+				)/>
+		<cfelse>
+			<cfreturn JavaObj.call
+				( Arguments.Passphrase
+				)/>
+		</cfif>
+	</cffunction>
+
+
+	<cffunction name="Check" returntype="String" output=false access="public">
+		<cfargument name="Passphrase" type="String" required />
+		<cfargument name="Hash"       type="String" required />
+		<cfargument name="Algorithm"  type="String" optional />
+
+		<cfset var JavaObj = createObject( 'java' , "sorcerersisle.cfpassphrase.coldfusion.PassphraseCheck" ) />
+
+		<cfif StructKeyExists(Arguments,'Algorithm')>
+			<cfreturn JavaObj.call
+				( Arguments.Passphrase
+				, Arguments.Hash
+				, Arguments.Algorithm
+				)/>
+		<cfelse>
+			<cfreturn JavaObj.call
+				( Arguments.Passphrase
+				, Arguments.Hash
+				)/>
+		</cfif>
+	</cffunction>
+
+
+	<cffunction name="Info" returntype="Struct" output=false access="public">
+		<cfargument name="Hash"       type="String" required />
+		<cfargument name="Algorithm"  type="String" optional />
+
+		<cfset var JavaObj = createObject( 'java' , "sorcerersisle.cfpassphrase.coldfusion.PassphraseInfo" ) />
+
+		<cfif StructKeyExists(Arguments,'Algorithm')>
+			<cfreturn JavaObj.call
+				( Arguments.Hash
+				, Arguments.Algorithm
+				)/>
+		<cfelse>
+			<cfreturn JavaObj.call
+				( Arguments.Hash
+				)/>
+		</cfif>
+	</cffunction>
+
+
+</cfcomponent>
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/coldfusion/Passphrase.cfm b/src/sorcerersisle/cfpassphrase/coldfusion/Passphrase.cfm
new file mode 100644
index 0000000..d8cff33
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/coldfusion/Passphrase.cfm
@@ -0,0 +1,106 @@
+<cfif NOT StructKeyExists(Attributes,'Action')>
+	<cfthrow
+		type    = "cfPassphrase.ColdFusion.Tag.MissingAttribute.Action"
+		message = "You must specify the [Action] attribute. Accepted values are 'hash', 'check', or 'info'."
+	/>
+</cfif>
+<cfif NOT StructKeyExists(Attributes,'Variable')>
+	<cfthrow
+		type    = "cfPassphrase.ColdFusion.Tag.MissingAttribute.Variable"
+		message = "You must specify the [Variable] attribute."
+	/>
+</cfif>
+<cfif NOT StructKeyExists(Attributes,'Passphrase')>
+	<cfthrow
+		type    = "cfPassphrase.ColdFusion.Tag.MissingAttribute.Passphrase"
+		message = "You must specify the [Passphrase] attribute."
+	/>
+</cfif>
+
+<cfswitch expression=#Attributes.Action# >
+
+	<cfcase value="hash">
+		<cfset JavaObj = createObject( 'java' , "sorcerersisle.cfpassphrase.coldfusion.PassphraseHash" ) />
+
+		<cfif StructKeyExists(Attributes,'AlgorithmParams')>
+			<cfif NOT StructKeyExists(Attributes,'Algorithm')>
+				<cfthrow
+					type    = "cfPassphrase.ColdFusion.Tag.MissingAttribute.Algorithm"
+					message = "You must specify the [Algorithm] attribute when [AlgorithmParams] is specified."
+				/>
+			</cfif>
+			<cfset Caller[Attributes.Variable] = JavaObj.call
+				( Attributes.Passphrase
+				, Attributes.Algorithm
+				, Attributes.AlgorithmParams
+				)/>
+		<cfelseif StructKeyExists(Attributes,'Algorithm')>
+			<cfset Caller[Attributes.Variable] = JavaObj.call
+				( Attributes.Passphrase
+				, Attributes.Algorithm
+				)/>
+		<cfelse>
+			<cfset Caller[Attributes.Variable] = JavaObj.call
+				( Attributes.Passphrase
+				)/>
+		</cfif>
+	</cfcase>
+
+	<cfcase value="check">
+		<cfif NOT StructKeyExists(Attributes,'Hash')>
+			<cfthrow
+				type    = "cfPassphrase.ColdFusion.Tag.MissingAttribute.Hash"
+				message = "You must specify the [Hash] attribute."
+			/>
+		</cfif>
+
+		<cfset JavaObj = createObject( 'java' , "sorcerersisle.cfpassphrase.coldfusion.PassphraseCheck" ) />
+
+		<cfif StructKeyExists(Attributes,'Algorithm')>
+			<cfset Caller[Attributes.Variable] = JavaObj.call
+				( Attributes.Passphrase
+				, Attributes.Hash
+				, Attributes.Algorithm
+				)/>
+		<cfelse>
+			<cfset Caller[Attributes.Variable] = JavaObj.call
+				( Attributes.Passphrase
+				, Attributes.Hash
+				)/>
+		</cfif>
+
+	</cfcase>
+
+	<cfcase value="info">
+		<cfif NOT StructKeyExists(Attributes,'Hash')>
+			<cfthrow
+				type    = "cfPassphrase.ColdFusion.Tag.MissingAttribute.Hash"
+				message = "You must specify the [Hash] attribute."
+			/>
+		</cfif>
+
+		<cfset JavaObj = createObject( 'java' , "sorcerersisle.cfpassphrase.coldfusion.PassphraseInfo" ) />
+
+		<cfif StructKeyExists(Attributes,'Algorithm')>
+			<cfset Caller[Attributes.Variable] = JavaObj.call
+				( Attributes.Hash
+				, Attributes.Algorithm
+				)/>
+		<cfelse>
+			<cfset Caller[Attributes.Variable] = JavaObj.call
+				( Attributes.Hash
+				)/>
+		</cfif>
+
+	</cfcase>
+
+	<cfdefaultcase>
+		<cfthrow
+			type    = "cfPassphrase.ColdFusion.Tag.InvalidAttribute.Action"
+			message = "Invalid value for [Action] attribute. Accepted values are 'hash', 'check', or 'info'."
+		/>
+	</cfdefaultcase>
+
+</cfswitch>
+
+<cfexit method="exitTag" />
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/coldfusion/PassphraseCheck.java b/src/sorcerersisle/cfpassphrase/coldfusion/PassphraseCheck.java
new file mode 100644
index 0000000..255fbab
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/coldfusion/PassphraseCheck.java
@@ -0,0 +1,26 @@
+package sorcerersisle.cfpassphrase.coldfusion;
+
+import sorcerersisle.cfpassphrase.*;
+
+
+public final class PassphraseCheck
+{
+
+
+	public static Boolean call
+		( String Passphrase , String Hash )
+	throws Exception
+	{ return call(Passphrase,Hash,null); }
+
+
+	public static Boolean call
+		( String      Passphrase
+		, String      Hash
+		, String      Algorithm
+		)
+	throws Exception
+	{
+		return Impl.check(Passphrase,Hash,Algorithm);
+	}
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/coldfusion/PassphraseHash.java b/src/sorcerersisle/cfpassphrase/coldfusion/PassphraseHash.java
new file mode 100644
index 0000000..29b7fda
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/coldfusion/PassphraseHash.java
@@ -0,0 +1,39 @@
+package sorcerersisle.cfpassphrase.coldfusion;
+
+import sorcerersisle.cfpassphrase.*;
+import coldfusion.runtime.Struct;
+import java.util.Map;
+
+
+public final class PassphraseHash
+{
+
+
+	public static String call
+		( String Passphrase )
+	throws Exception
+	{ return call(Passphrase,null,null); }
+
+	public static String call
+		( String Passphrase , String Algorithm )
+	throws Exception
+	{ return call(Passphrase,Algorithm,null); }
+
+
+	@SuppressWarnings("unchecked")
+	public static String call
+		( String Passphrase
+		, String Algorithm
+		, Struct AlgorithmParams
+		)
+	throws Exception
+	{
+		return Impl.hash
+			( Passphrase
+			, Algorithm
+			, (Map<String,Object>) AlgorithmParams
+			);
+	}
+
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/coldfusion/PassphraseInfo.java b/src/sorcerersisle/cfpassphrase/coldfusion/PassphraseInfo.java
new file mode 100644
index 0000000..19e9272
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/coldfusion/PassphraseInfo.java
@@ -0,0 +1,30 @@
+package sorcerersisle.cfpassphrase.coldfusion;
+
+import sorcerersisle.cfpassphrase.*;
+import coldfusion.runtime.Struct;
+
+
+public final class PassphraseInfo
+{
+
+
+	public static Struct call
+		( String Passphrase )
+	throws Throwable
+	{ return call(Passphrase,null); }
+
+
+	public static Struct call
+		( String Hash
+		, String Algorithm
+		)
+	throws Throwable
+	{
+		Struct Result = new Struct();
+
+		Result.putAll(Impl.info(Hash,Algorithm));
+
+		return Result;
+	}
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/openbd/PassphraseCheck.java b/src/sorcerersisle/cfpassphrase/openbd/PassphraseCheck.java
new file mode 100644
index 0000000..c9c0310
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/openbd/PassphraseCheck.java
@@ -0,0 +1,46 @@
+package sorcerersisle.cfpassphrase.openbd;
+
+import sorcerersisle.cfpassphrase.*;
+import com.naryx.tagfusion.expression.function.functionBase;
+import com.naryx.tagfusion.cfm.engine.cfArgStructData;
+import com.naryx.tagfusion.cfm.engine.cfBooleanData;
+import com.naryx.tagfusion.cfm.engine.cfSession;
+import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
+
+@SuppressWarnings("serial")
+public final class PassphraseCheck
+	extends functionBase
+{
+
+	public PassphraseCheck()
+	{
+		min = 2;
+		max = 3;
+		setNamedParams( new String[] { "Passphrase" , "Hash" , "Algorithm" } );
+	}
+
+
+	public cfBooleanData execute
+		( cfSession _session
+		, cfArgStructData ArgStruct
+		)
+	throws cfmRunTimeException
+	{
+		try
+		{
+			String Passphrase = getNamedStringParam( ArgStruct , "Passphrase" , null );
+			String Hash       = getNamedStringParam( ArgStruct , "Hash"       , null );
+			String Algorithm  = getNamedStringParam( ArgStruct , "Algorithm"  , null );
+
+			return cfBooleanData.getcfBooleanData
+				( Impl.check
+					( Passphrase , Hash , Algorithm )
+				);
+		}
+		catch(Exception Ex)
+		{
+			throw new cfmRunTimeException(_session,Ex);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/openbd/PassphraseHash.java b/src/sorcerersisle/cfpassphrase/openbd/PassphraseHash.java
new file mode 100644
index 0000000..a4e6388
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/openbd/PassphraseHash.java
@@ -0,0 +1,52 @@
+package sorcerersisle.cfpassphrase.openbd;
+
+import sorcerersisle.cfpassphrase.*;
+import com.naryx.tagfusion.expression.function.functionBase;
+import com.naryx.tagfusion.cfm.engine.cfSession;
+import com.naryx.tagfusion.cfm.engine.cfArgStructData;
+import com.naryx.tagfusion.cfm.engine.cfStringData;
+import com.naryx.tagfusion.cfm.engine.cfStructData;
+import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
+
+@SuppressWarnings("serial")
+public final class PassphraseHash
+	extends functionBase
+{
+
+	public PassphraseHash()
+	{
+		min = 1;
+		max = 3;
+		setNamedParams( new String[] { "Passphrase" , "Algorithm" , "AlgorithmParams" } );
+	}
+
+
+	@SuppressWarnings("unchecked")
+	public cfStringData execute
+		( cfSession _session
+		, cfArgStructData ArgStruct
+		)
+	throws cfmRunTimeException
+	{
+		try
+		{
+			String Passphrase = getNamedStringParam( ArgStruct , "Passphrase" , null );
+			String Algorithm  = getNamedStringParam( ArgStruct , "Algorithm"  , null );
+			cfStructData AlgorithmParams = (cfStructData) getNamedParam( ArgStruct , "AlgorithmParams" , null );
+
+			return new cfStringData
+				( Impl.hash
+					( Passphrase
+					, Algorithm
+					, AlgorithmParams
+					)
+				);
+
+		}
+		catch(Exception Ex)
+		{
+			throw new cfmRunTimeException(_session,Ex);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/openbd/PassphraseInfo.java b/src/sorcerersisle/cfpassphrase/openbd/PassphraseInfo.java
new file mode 100644
index 0000000..756c7a5
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/openbd/PassphraseInfo.java
@@ -0,0 +1,47 @@
+package sorcerersisle.cfpassphrase.openbd;
+
+import sorcerersisle.cfpassphrase.*;
+import com.naryx.tagfusion.expression.function.functionBase;
+import com.naryx.tagfusion.cfm.engine.cfArgStructData;
+import com.naryx.tagfusion.cfm.engine.cfData;
+import com.naryx.tagfusion.cfm.engine.cfSession;
+import com.naryx.tagfusion.cfm.engine.cfStructData;
+import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
+
+
+@SuppressWarnings("serial")
+public final class PassphraseInfo
+	extends functionBase
+{
+
+	public PassphraseInfo()
+	{
+		min = 1;
+		max = 2;
+		setNamedParams( new String[] { "Hash" , "Algorithm" } );
+	}
+
+
+	public cfData execute
+		( cfSession _session
+		, cfArgStructData ArgStruct
+		)
+	throws cfmRunTimeException
+	{
+		try
+		{
+			String Hash       = getNamedStringParam( ArgStruct , "Hash"       , null );
+			String Algorithm  = getNamedStringParam( ArgStruct , "Algorithm"  , null );
+
+			cfStructData Result = new cfStructData();
+			Result.putAll( Impl.info(Hash,Algorithm) );
+
+			return Result;
+		}
+		catch(Exception Ex)
+		{
+			throw new cfmRunTimeException(_session,Ex);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/openbd/PassphraseTag.java b/src/sorcerersisle/cfpassphrase/openbd/PassphraseTag.java
new file mode 100644
index 0000000..4fc4715
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/openbd/PassphraseTag.java
@@ -0,0 +1,81 @@
+package sorcerersisle.cfpassphrase.openbd;
+
+import com.naryx.tagfusion.cfm.tag.cfTag;
+import com.naryx.tagfusion.cfm.tag.cfTagReturnType;
+import com.naryx.tagfusion.cfm.engine.cfArgStructData;
+import com.naryx.tagfusion.cfm.engine.cfSession;
+import com.naryx.tagfusion.cfm.engine.cfData;
+import com.naryx.tagfusion.cfm.engine.cfStructData;
+import com.naryx.tagfusion.cfm.engine.cfmBadFileException;
+import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
+
+@SuppressWarnings("serial")
+public final class PassphraseTag
+	extends cfTag
+{
+
+
+	protected void defaultParameters
+		( String _tag )
+	throws cfmBadFileException
+	{
+		parseTagHeader( _tag );
+
+		if ( ! containsAttribute("action") )
+			throw newBadFileException( "You must specify the [Action] attribute. Accepted values are 'hash', 'check', 'info'." , "" );
+
+		if ( ! containsAttribute("variable") )
+			throw newBadFileException( "You must specify the [Variable] attribute." , "" );
+
+		if ( ! containsAttribute("passphrase") )
+			throw newBadFileException( "You must specify the [Passphrase] attribute." , "" );
+	}
+
+
+	public cfTagReturnType render
+		( cfSession _session )
+	throws cfmRunTimeException
+	{
+		String Action        = getDynamic(_session,"action").getString();
+		String Variable      = getDynamic(_session, "variable").getString();
+		cfArgStructData Args = new cfArgStructData();
+		cfData Result;
+
+		if (Action.equalsIgnoreCase("hash"))
+		{
+			Args.setData("passphrase",  getDynamic(_session, "passphrase").getString());
+			if (containsAttribute("algorithm")) Args.setData("algorithm",  getDynamic(_session, "algorithm").getString());
+			if (containsAttribute("algorithmparams")) Args.setData("algorithmparams",  (cfStructData)getDynamic(_session, "algorithmparams"));
+
+			Result = new PassphraseHash().execute(_session,Args);
+		}
+		else if (Action.equalsIgnoreCase("check"))
+		{
+			Args.setData("passphrase" , getDynamic(_session, "passphrase").getString());
+			Args.setData("hash"       , getDynamic(_session, "hash").getString());
+			if (containsAttribute("algorithm")) Args.setData("algorithm",  getDynamic(_session, "algorithm").getString());
+
+			Result = new PassphraseCheck().execute(_session,Args);
+		}
+		else if (Action.equalsIgnoreCase("info"))
+		{
+			Args.setData("hash"       , getDynamic(_session, "hash").getString());
+			if (containsAttribute("algorithm")) Args.setData("algorithm",  getDynamic(_session, "algorithm").getString());
+
+			Result = new PassphraseCheck().execute(_session,Args);
+		}
+		else
+		{
+			throw new cfmRunTimeException
+				( _session
+				, new Exception("Invalid value ["+Action+"] for [Action] attribute. Accepted values are 'hash', 'check', 'info'.")
+				);
+		}
+
+		_session.setData( Variable , Result );
+
+		return cfTagReturnType.NORMAL;
+	}
+
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/openbd/cfPassphrasePlugin.java b/src/sorcerersisle/cfpassphrase/openbd/cfPassphrasePlugin.java
new file mode 100644
index 0000000..6e3efde
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/openbd/cfPassphrasePlugin.java
@@ -0,0 +1,42 @@
+package sorcerersisle.cfpassphrase.openbd;
+
+import com.bluedragon.plugin.Plugin;
+import com.bluedragon.plugin.PluginManagerInterface;
+import com.naryx.tagfusion.xmlConfig.xmlCFML;
+
+public final class cfPassphrasePlugin implements Plugin
+{
+
+	public cfPassphrasePlugin()
+	{
+
+	}
+
+	public String getPluginName()
+	{
+		return "cfPassphrase";
+	}
+
+	public String getPluginVersion()
+	{
+		return "0.0.000";
+	}
+
+	public String getPluginDescription()
+	{
+		return "Provides functions PassphraseHash, PassphraseCheck and PassphraseInfo to OpenBD.";
+	}
+
+	public void pluginStart( PluginManagerInterface Manager, xmlCFML SystemParameters )
+	{
+		Manager.registerFunction("PassphraseHash" ,"sorcerersisle.cfpassphrase.openbd.PassphraseHash");
+		Manager.registerFunction("PassphraseCheck","sorcerersisle.cfpassphrase.openbd.PassphraseCheck");
+		Manager.registerFunction("PassphraseInfo","sorcerersisle.cfpassphrase.openbd.PassphraseInfo");
+		Manager.registerTag("cfpassphrase","sorcerersisle.cfpassphrase.openbd.PassphraseTag");
+	}
+
+	public void pluginStop( PluginManagerInterface Manager )
+	{
+	}
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/railo/PassphraseCheck.java b/src/sorcerersisle/cfpassphrase/railo/PassphraseCheck.java
new file mode 100644
index 0000000..f020407
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/railo/PassphraseCheck.java
@@ -0,0 +1,40 @@
+package sorcerersisle.cfpassphrase.railo;
+
+import sorcerersisle.cfpassphrase.*;
+import railo.runtime.ext.function.Function;
+import railo.runtime.PageContext;
+import railo.runtime.exp.PageException;
+import railo.loader.engine.CFMLEngineFactory;
+
+
+@SuppressWarnings("serial")
+public final class PassphraseCheck
+	implements Function
+{
+
+
+	public static Boolean call
+		( PageContext pc , String Passphrase , String Hash )
+	throws PageException
+	{ return call(pc,Passphrase,Hash,null); }
+
+
+	public static Boolean call
+		( PageContext pc
+		, String      Passphrase
+		, String      Hash
+		, String      Algorithm
+		)
+	throws PageException
+	{
+		try
+		{
+			return Impl.check( Passphrase , Hash , Algorithm );
+		}
+		catch(Exception Ex)
+		{
+			throw CFMLEngineFactory.getInstance().getCastUtil().toPageException( Ex );
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/railo/PassphraseHash.java b/src/sorcerersisle/cfpassphrase/railo/PassphraseHash.java
new file mode 100644
index 0000000..3c5c1e6
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/railo/PassphraseHash.java
@@ -0,0 +1,56 @@
+package sorcerersisle.cfpassphrase.railo;
+
+import sorcerersisle.cfpassphrase.*;
+import railo.runtime.ext.function.Function;
+import railo.runtime.PageContext;
+import railo.runtime.exp.PageException;
+import railo.loader.engine.CFMLEngineFactory;
+import railo.runtime.type.Struct;
+import railo.runtime.util.Cast;
+
+
+@SuppressWarnings("serial")
+public final class PassphraseHash
+	implements Function
+{
+
+
+	public static String call
+		( PageContext pc , String Passphrase )
+	throws PageException
+	{ return call(pc,Passphrase,null,null); }
+
+	public static String call
+		( PageContext pc , String Passphrase , String Algorithm )
+	throws PageException
+	{ return call(pc,Passphrase,Algorithm,null); }
+
+
+	@SuppressWarnings("unchecked")
+	public static String call
+		( PageContext pc
+		, String      Passphrase
+		, String      Algorithm
+		, Struct      AlgorithmParams
+		)
+	throws PageException
+	{
+		Cast Caster = CFMLEngineFactory.getInstance().getCastUtil();
+
+		try
+		{
+			return Impl.hash
+				( Passphrase
+				, Algorithm
+				, Caster.toMap(AlgorithmParams,null)
+				);
+		}
+		catch(Exception Ex)
+		{
+			throw Caster.toPageException( Ex );
+		}
+
+	}
+
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/railo/PassphraseInfo.java b/src/sorcerersisle/cfpassphrase/railo/PassphraseInfo.java
new file mode 100644
index 0000000..b956d53
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/railo/PassphraseInfo.java
@@ -0,0 +1,43 @@
+package sorcerersisle.cfpassphrase.railo;
+
+import sorcerersisle.cfpassphrase.*;
+import railo.runtime.ext.function.Function;
+import railo.runtime.PageContext;
+import railo.runtime.exp.PageException;
+import railo.runtime.type.Struct;
+import railo.runtime.util.Cast;
+import railo.loader.engine.CFMLEngineFactory;
+
+
+@SuppressWarnings("serial")
+public final class PassphraseInfo
+	implements Function
+{
+
+
+	public static Struct call
+		( PageContext pc , String Passphrase )
+	throws PageException
+	{ return call(pc,Passphrase,null); }
+
+
+	public static Struct call
+		( PageContext pc
+		, String      Hash
+		, String      Algorithm
+		)
+	throws PageException
+	{
+		Cast Caster = CFMLEngineFactory.getInstance().getCastUtil();
+
+		try
+		{
+			return Caster.toStruct( Impl.info(Hash,Algorithm) );
+		}
+		catch(Exception Ex)
+		{
+			throw Caster.toPageException( Ex );
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/railo/PassphraseTag.java b/src/sorcerersisle/cfpassphrase/railo/PassphraseTag.java
new file mode 100644
index 0000000..e37afb1
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/railo/PassphraseTag.java
@@ -0,0 +1,92 @@
+package sorcerersisle.cfpassphrase.railo;
+
+import railo.runtime.ext.tag.TagSupport;
+import railo.runtime.ext.tag.DynamicAttributes;
+import railo.runtime.type.Collection.Key;
+import railo.runtime.type.Struct;
+import railo.runtime.exp.PageException;
+import railo.loader.engine.CFMLEngineFactory;
+
+@SuppressWarnings("deprecation")
+public final class PassphraseTag
+	extends TagSupport
+	implements DynamicAttributes
+{
+
+
+	private Struct Attributes = CFMLEngineFactory.getInstance().getCreationUtil().createStruct();
+
+
+	public int doStartTag()
+	throws PageException
+	{
+		String Action          = (String)Attributes.get("action",null);
+		String Variable        = (String)Attributes.get("variable",null);
+		String Passphrase      = (String)Attributes.get("passphrase",null);
+		String Algorithm       = (String)Attributes.get("algorithm",null);
+		Object Result;
+
+		if (Action.equalsIgnoreCase("hash"))
+		{
+			Struct AlgorithmParams = Attributes.containsKey("algorithmparams") ? (Struct)Attributes.get("algorithmparams") : null;
+
+			Result = PassphraseHash.call
+				( pageContext
+				, Passphrase
+				, Algorithm
+				, AlgorithmParams
+				);
+		}
+		else if (Action.equalsIgnoreCase("check"))
+		{
+			String Hash = (String)Attributes.get("hash",null);
+
+			Result = PassphraseCheck.call
+				( pageContext
+				, Passphrase
+				, Hash
+				, Algorithm
+				);
+		}
+		else if (Action.equalsIgnoreCase("info"))
+		{
+			String Hash = (String)Attributes.get("hash",null);
+
+			Result = PassphraseCheck.call
+				( pageContext
+				, Hash
+				, Algorithm
+				);
+		}
+		else
+		{
+			throw CFMLEngineFactory.getInstance().getCastUtil().toPageException
+				( new Exception("Invalid value for [Action] attribute. Accepted values are 'hash','check', or [info].")
+				);
+		}
+
+		pageContext.setVariable( Variable , Result );
+
+
+		return SKIP_BODY;
+	}
+
+
+	public void setDynamicAttribute(String uri, String localName, Object value)
+	{
+		Attributes.setEL(localName,value);
+	}
+
+	public void setDynamicAttribute(String uri, Key localName, Object value)
+	{
+		Attributes.setEL(localName,value);
+	}
+
+
+	public void release()
+	{
+		super.release();
+		Attributes.clear();
+	}
+
+}
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/railo/cfPassphrase.fld b/src/sorcerersisle/cfpassphrase/railo/cfPassphrase.fld
new file mode 100644
index 0000000..81f29e6
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/railo/cfPassphrase.fld
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE func-lib PUBLIC "-//Railo//DTD CFML Function Library 1.0//EN" "dtd/web-cfmfunctionlibrary_1_0.dtd">
+<func-lib>
+	<flib-version>1.00</flib-version>
+
+	<short-name>cfpassphrase-functions</short-name>
+	<uri>http://sorcerersisle.com/projects:cfpassphrase.html</uri>
+	<display-name>cfPassphrase Functions</display-name>
+	<description>Provides functions PassphraseHash and PassphraseCheck to Railo.</description>
+
+	<function>
+		<name>PassphraseHash</name>
+		<class>sorcerersisle.cfpassphrase.railo.PassphraseHash</class>
+		<description>PassphraseHash returns a hash using the specified KDF algorithm.</description>
+		<argument><name>Passphrase</name>      <type>String</type> <required>yes</required> <description>The passphrase to hash.</description>                   </argument>
+		<argument><name>Algorithm</name>       <type>String</type> <required>no</required>  <description>The algorithm to use. ()Default is bcrypt</description> </argument>
+		<argument><name>AlgorithmParams</name> <type>Struct</type> <required>no</required>  <description>Any parameters to pass into the algorithm.</description></argument>
+		<return><type>String</type></return>
+	</function>
+
+	<function>
+		<name>PassphraseCheck</name>
+		<class>sorcerersisle.cfpassphrase.railo.PassphraseCheck</class>
+		<description>PassphraseCheck returns true/false depending on whether the passphrase matches the provided hash, according to the algorithm provided.</description>
+		<argument><name>Passphrase</name> <type>String</type> <required>yes</required> <description>The passphrase to check.</description>                                  </argument>
+		<argument><name>Hash</name>       <type>String</type> <required>yes</required> <description>The existing passphrase hash.</description>                             </argument>
+		<argument><name>Algorithm</name>  <type>String</type> <required>no</required>  <description>The algorithm to use. (Default depends on hash signature.)</description></argument>
+		<return><type>Boolean</type></return>
+	</function>
+
+	<function>
+		<name>PassphraseInfo</name>
+		<class>sorcerersisle.cfpassphrase.railo.PassphraseInfo</class>
+		<description>PassphraseInfo Identifies the algorithm and parameters for the specified hash.</description>
+		<argument><name>Hash</name>      <type>String</type> <required>yes</required> <description>The hash to return info about.</description>                            </argument>
+		<argument><name>Algorithm</name> <type>String</type> <required>no</required>  <description>The algorithm to use. (Default depends on hash signature.)</description></argument>
+		<return><type>Struct</type></return>
+	</function>
+
+</func-lib>
\ No newline at end of file
diff --git a/src/sorcerersisle/cfpassphrase/railo/cfPassphrase.tld b/src/sorcerersisle/cfpassphrase/railo/cfPassphrase.tld
new file mode 100644
index 0000000..e075d1e
--- /dev/null
+++ b/src/sorcerersisle/cfpassphrase/railo/cfPassphrase.tld
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE taglib PUBLIC "-//Railo//DTD CFML Tag Library 1.0//EN" "dtd/web-cfmtaglibrary_1_0.dtd">
+<taglib>
+	<tlib-version>1.00</tlib-version>
+
+	<short-name>cfpassphrase-tag</short-name>
+	<uri>http://sorcerersisle.com/projects:cfpassphrase.html</uri>
+	<display-name>cfPassphrase Tag</display-name>
+	<description>Provides tag cfpassphrase to Railo.</description>
+
+	<name-space>cf</name-space>
+	<name-space-separator/>
+
+	<tag>
+		<name>passphrase</name>
+		<tag-class>sorcerersisle.cfpassphrase.railo.PassphraseTag</tag-class>
+		<body-content>empty</body-content>
+		<body-rtexprvalue>false</body-rtexprvalue>
+		<description>Checks or calculates a hash using the specified KDF algorithm.</description>
+		<attribute-type>dynamic</attribute-type>
+		<attribute-min>3</attribute-min>
+		<attribute-max>6</attribute-max>
+		<attribute>
+			<name>Action</name>
+			<type>string</type>
+			<required>true</required>
+			<description>Specify [hash], [check], or [info] to determine what action tag takes.</description>
+		</attribute>
+		<attribute>
+			<name>Variable</name>
+			<type>string</type>
+			<required>true</required>
+			<description>Name of variable to contain result of action.</description>
+		</attribute>
+		<attribute>
+			<name>Passphrase</name>
+			<type>string</type>
+			<required>false</required>
+			<description>The passphrase to be hashed or checked. (action=hash,action=check)</description>
+		</attribute>
+		<attribute>
+			<name>Hash</name>
+			<type>string</type>
+			<required>false</required>
+			<description>The existing hash to be checked against. (action=check,action=info)</description>
+		</attribute>
+		<attribute>
+			<name>Algorithm</name>
+			<type>string</type>
+			<required>false</required>
+			<description>Algorithm to be used.</description>
+		</attribute>
+		<attribute>
+			<name>AlgorithmParams</name>
+			<type>struct</type>
+			<required>false</required>
+			<description>Optional parameters to be used by algorithm. (action=hash)</description>
+		</attribute>
+	</tag>
+
+</taglib>
\ No newline at end of file