如何派生 AWS 签名版本 4(在 ColdFusion 中)的登录密钥?

How to derive a sign-in key for AWS Signature Version 4 (in ColdFusion)?

我正在尝试使用 ColdFusion 访问 Amazon Web Services (AWS),使用他们当前称为签名版本 4 的身份验证方法。我查阅了他们的文档,其中包含 several programming languages, as well as pseudo-code for other languages 的代码示例。他们提供了一些测试输入值以传递到我的脚本的签名函数,以及一些预期结果。

这里是测试输入:

key = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
dateStamp = '20120215'
regionName = 'us-east-1'
serviceName = 'iam'

这是预期的结果:

kSecret  = '41575334774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559'
kDate    = '969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d'
kRegion  = '69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c'
kService = 'f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa'
kSigning = 'f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d'

"kSigning" 的正确值应该是这样的:

f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d

但是,对于 "kSigning",我的代码生成了这个:

31A84DCE0538A8B15ED68CCFBD803F17947E41BF625EFFD1AD6A67FC821F9BE2

我正在使用 Railo 4.2。有人可以帮我解决这个问题,以便预期值与倾销值相匹配吗?这是我的 ColdFusion 标记:

<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 kService = sign( arguments.serviceName, kRegion ) />
<cfset var kSigning = sign( arguments.serviceName, kService ) />

红旗警报,两次 hmac 服务名称有意义吗?

最后一步的输入是字符串文字。

<cfset var kSigning = sign( "aws4_request", kService ) />

<cfset var kRegion = sign( arguments.regionName, kDate ) />

我对代码运行没有错误感到惊讶,因为 sign() 函数需要两个字符串,但代码实际上为第二个参数传递了一个字节数组。 (在 CF11 下会抛出错误)。也许正在进行某种隐式转换? 无论如何,在稍微重构函数之后,该示例工作正常,但有一个例外。该示例的最后一行使用文字字符串 "aws4_request" 而不是 "arguments.serviceName"。请参阅下面的示例。

话虽如此,Railo 难道没有一个 HMAC 函数,您可以使用它而不是自己滚动?我猜是这样,因为 HMAC() is included in CF10+. Update: As , Railo added the HMAC() function in 4.0.0.011。但是,为了向后兼容,下面的 java 版本应该适用于大多数任何版本。

示例:

result = getSignatureKey(key, dateStamp, regionName, serviceName);
writeDump( binaryEncode(result, "hex") );

结果:

F4780E2D9F65FA895F9C67B32CE1BAF0B0D8A43505A000A1A9E090D414DB404D 

函数:

<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>

我相信您可以使用内置的 HMAC() 函数在 ColdFusion 10+ 中执行此操作,而无需创建 Java 对象:

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;
}