I am trying to use ColdFusion to access Amazon Web Services (AWS) using their current authentication method known as Signature Version 4. I have consulted their documentation which has code examples for several programming languages, as well as pseudo-code for other languages. They provided some test input values to pass in to my script's signature function, and also some expected results.
Here are the test inputs:
key = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
dateStamp = '20120215'
regionName = 'us-east-1'
serviceName = 'iam'
Here are the expected results:
kSecret = '41575334774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559'
kDate = '969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d'
kRegion = '69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c'
kService = 'f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa'
kSigning = 'f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d'
The correct value for "kSigning" should be this:
f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d
However, for "kSigning" my code generates this:
31A84DCE0538A8B15ED68CCFBD803F17947E41BF625EFFD1AD6A67FC821F9BE2
I am using Railo 4.2. Can someone please help me solve this, so that the anticipated value matches the dumped value? Here's my ColdFusion markup:
<cfsilent>
<!--- HMACSHA256 --->
<cffunction name="sign" returntype="binary" access="private" output="false" hint="Sign with NSA SHA-256 Algorithm">
<cfargument name="signMessage" type="string" required="true" />
<cfargument name="signKey" type="string" required="true" />
<cfset var jMsg = JavaCast("string",arguments.signMessage).getBytes("utf-8") />
<cfset var jKey = JavaCast("string",arguments.signKey).getBytes("utf-8") />
<cfset var key = createObject("java","javax.crypto.spec.SecretKeySpec") />
<cfset var mac = createObject("java","javax.crypto.Mac") />
<cfset key = key.init(jKey,"HmacSHA256") />
<cfset mac = mac.getInstance(key.getAlgorithm()) />
<cfset mac.init(key) />
<cfreturn mac.doFinal(jMsg) />
</cffunction>
<!--- Get Signature Key --->
<cffunction name="getSignatureKey" returntype="binary" access="private" output="false" hint="Derive the sign-in key">
<cfargument name="key" type="string" required="true" />
<cfargument name="dateStamp" type="string" required="true" />
<cfargument name="regionName" type="string" required="true" />
<cfargument name="serviceName" type="string" required="true" />
<cfset var kSecret = "AWS4" & arguments.key />
<cfset var kDate = sign( arguments.dateStamp, kSecret ) />
<cfset var kRegion = sign( arguments.regionName, kDate ) />
<cfset var kService = sign( arguments.serviceName, kRegion ) />
<cfset var kSigning = sign( arguments.serviceName, kService ) />
<cfreturn kSigning />
</cffunction>
</cfsilent><!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>AWS Test</title>
</head>
<body>
<cfset kSecret = getSignatureKey(
'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY',
'20120215',
'us-east-1',
'iam'
) />
<cfdump var="#BinaryEncode(kSecret, 'hex')#" label="kSecret" />
</body>
</html>
<cfset var kRegion = sign( arguments.regionName, kDate ) />
I am little surprised the code runs without error, as the sign()
function expects two strings, but the code is actually passing in a byte array for the second parameter. (Under CF11 it throws an error). Perhaps there is some sort of implicit conversion going on?
Anyway, after refactoring the functions slightly, the example worked fine with one exception. The very last line of the example uses the literal string "aws4_request" rather than "arguments.serviceName". See example below.
Having said that, does not Railo have an HMAC function you can use rather than rolling your own? I am guessing so, as HMAC() is included in CF10+. Update: As vrtjason noted in the comments, Railo added the HMAC()
function in 4.0.0.011. However, for backward compatibility the java version below should work with most any version.
Example:
result = getSignatureKey(key, dateStamp, regionName, serviceName);
writeDump( binaryEncode(result, "hex") );
Results:
F4780E2D9F65FA895F9C67B32CE1BAF0B0D8A43505A000A1A9E090D414DB404D
Functions:
<cffunction name="getSignatureKey" returntype="binary" access="private" output="false" hint="Derive the sign-in key">
<cfargument name="key" type="string" required="true" />
<cfargument name="dateStamp" type="string" required="true" />
<cfargument name="regionName" type="string" required="true" />
<cfargument name="serviceName" type="string" required="true" />
<cfset Local.kSecret = charsetDecode("AWS4" & arguments.key, "UTF-8") />
<cfset Local.kDate = sign( arguments.dateStamp, Local.kSecret ) />
<cfset Local.kRegion = sign( arguments.regionName, Local.kDate ) />
<cfset Local.kService = sign( arguments.serviceName, Local.kRegion ) />
<cfset Local.kSigning = sign( "aws4_request", Local.kService ) />
<cfreturn Local.kSigning />
</cffunction>
<cffunction name="sign" returntype="binary" access="private" output="false" hint="Sign with NSA SHA-256 Algorithm">
<cfargument name="message" type="string" required="true" />
<cfargument name="key" type="binary" required="true" />
<cfargument name="algorithm" type="string" default="HmacSHA256" />
<cfargument name="encoding" type="string" default="UTF-8" />
<cfset Local.keySpec = createObject("java","javax.crypto.spec.SecretKeySpec") />
<cfset Local.keySpec = Local.keySpec.init( arguments.key, arguments.algorithm ) />
<cfset Local.mac = createObject("java","javax.crypto.Mac").getInstance( arguments.algorithm ) />
<cfset Local.mac.init( Local.keySpec ) />
<cfreturn Local.mac.doFinal( charsetDecode(arguments.message, arguments.encoding ) ) />
</cffunction>
I see a problem and expect you will see it, too, when you note how similar these two lines are:
<cfset var kService = sign( arguments.serviceName, kRegion ) />
<cfset var kSigning = sign( arguments.serviceName, kService ) />
Red flag alert, does it make sense to hmac the service name twice?
The input to the last step is a string literal.
<cfset var kSigning = sign( "aws4_request", kService ) />
I believe you can do this in ColdFusion 10+ using the built in HMAC() function without needing to Create a Java Object:
function getSignatureKey(key, dateStamp, regionName, serviceName) {
var kDate= lCase(HMAC(ARGUMENTS.dateStamp,"AWS4" & ARGUMENTS.key,"hmacsha256"));
var kRegion= lCase(HMAC(ARGUMENTS.regionName,binaryDecode(kDate,'hex'),"hmacsha256"));
var kService=lCase(HMAC(ARGUMENTS.serviceName,binaryDecode(kRegion,'hex'),"hmacsha256"));
var kSigning= lCase(HMAC("aws4_request",binaryDecode(kService,'hex'),"hmacsha256"));
return kSigning;
}
来源:https://stackoverflow.com/questions/32513197/how-to-derive-a-sign-in-key-for-aws-signature-version-4-in-coldfusion