使用 ColdFusion 为单点登录的数据签名

Using ColdFusion to sign data for single sign-on

对于本文的篇幅,我提前表示歉意post。我对这个问题的了解还不够,无法正确识别具体问题的实际含义!但无论如何,我们一直在针对我们的会员 API 拨打电话,以使用@Leigh 提供的步骤和建议查询有关我们会员的信息(加入日期、会员类型等),他们一直工作得很好!再次感谢 Leigh,我们的成员很高兴能够做到这一点!

现在我想为我们的会员设置单点登录,允许他们在我们的页面上登录,然后快速转到他们已经登录该站点的会员资料。根据 API documentation,我需要做的一件事是:

"Use your Signing Certificate to sign the Portal username of the person to log in."

我完全坚持这一点。我收到了一个 XML 私钥(由他们的 .NET 应用程序生成),格式为

<RSAKeyValue><Modulus>{stuff}</Modulus><Exponent>{stuff}</Exponent><P>... etc etc

我了解到我无法直接使用此格式,必须将其转换为 PEM 格式或类似格式。使用 OpenSSL,我想我已经做到了,现在有一个格式为“-----BEGIN PRIVATE KEY-----{stuff}-----END PRIVATE KEY-----”的文件。

使用 Leigh 的解决方案确实给了我一个签名,但它与 API 文档中提供的示例不匹配。我认为这是因为它使用 HmacSHA1,而他们注意到 "the signature in the header uses HMAC SHA1 whereas the signature for creating security tokens uses a public/private keypair and RSA-SHA1. The same method cannot be used to generate both." 我尝试更改

<cfset key = key.init(jKey,"HmacSHA1") />

<cfset key = key.init(jKey,"RSA-SHA1") />

得到了"Algorithm RSA-SHA1 not available."

我已经尝试复制和粘贴其他一些建议的解决方案,但 none 有效。一个例子(来自12Robots.com):

<!--- Create a Java Cipher object and get a mode --->
<cfset cipher = createObject('java', 'javax.crypto.Cipher').getInstance("RSA") />

<!--- The mode tells the Cipher whether is will be encrypting or decrypting --->
<cfset encMode = cipher.ENCRYPT_MODE />

<cfset encryptedValue = "" /> <!--- Return variable --->

<!--- Initialize the Cipher with the mode and the key --->
<cfset cipher.init(encMode, key) />

<!--- Convert the string to bytes --->
<cfset stringBytes = stringToSign.getBytes("UTF8") />

<!--- Perform encryption --->
<cfset encryptedValue = cipher.doFinal(stringBytes, 0, len(inputString)) />

<cfdump var="#encryptedValue#">
本例中的

"Key" 是我之前提到的 PEM 文本,"stringToSign" 是用户名。我得到的错误是 "Either there are no methods with the specified method name and argument types or the init method is overloaded with argument types that ColdFusion cannot decipher reliably. ColdFusion found 0 methods that match the provided arguments. If this is a Java object and you verified that the method exists, use the javacast function to reduce ambiguity."

我试过的另一件事是:

<cfset rsaPrivateKey = toBase64(key, "utf-8")>

<cfset jKey = JavaCast("string", rsaPrivateKey)>
<cfset jMsg = JavaCast("string", stringToSign).getBytes("ASCII")>

<cfset key = createObject("java", "java.security.PrivateKey")>
<cfset keySpec = createObject("java", "java.security.spec.PKCS8EncodedKeySpec")>

<cfset keyFactory = createObject("java", "java.security.KeyFactory")>
<cfset b64dec = createObject("java", "sun.misc.BASE64Decoder")>
<cfset sig = createObject("java", "java.security.Signature")>

<cfset byteClass = createObject("java", "java.lang.Class")>
<cfset byteArray = createObject("java", "java.lang.reflect.Array")>

<cfset byteClass = byteClass.forName(JavaCast("string", "java.lang.Byte"))>
<cfset keyBytes = byteArray.newInstance(byteClass, JavaCast("int", "1024"))>
<cfset keyBytes = b64dec.decodeBuffer(jKey)>

<cfset sig = sig.getInstance("SHA1withRSA", "SunJSSE")>
<cfset sig.initSign(keyFactory.getInstance("RSA").generatePrivate(keySpec.init(keyBytes)))>
<cfset sig.update(jMsg)>
<cfset signBytes = sig.sign()>

<cfset finalSig = ToBase64(signBytes)>

<cfdump var="#finalSig#">

这给了我 "java.security.InvalidKeyException: invalid key format." 顺便说一句,如果我将 rsaPrivateKey 设置为 "key" 我会得到一个不同的错误,"java.security.InvalidKeyException: IOException : DerInputStream.getLength(): lengthTag=127, too big." 我很高兴收到不同的错误消息;至少有些事情正在发生! :-)

同样,我不知道这些 Java 函数在做什么。而且我肯定不明白为什么看似简单的事情最终变得如此复杂!但我怀疑,我要么错误地存储了私钥 PEM,要么错误地读出了数据库(或两者),这就是导致这些不同解决方案失败的原因。但我不知道是否是这种情况。

我欢迎任何可能对我有帮助的见解或建议!如果有人需要更多信息,我很乐意提供。非常感谢大家!

I gather that I am unable to work with this format directly and must convert it to PEM format or similar

这样做没有错,但技术上不需要。可以从 PEM 文件或 directly from the XML 加载密钥信息。

选项 1:从 XML 加载密钥:

将示例 XML 字符串解析为一个对象。然后提取模数和 private 指数(即 <D> 元素)。使用模数和指数创建一个 RSAPrivateKeySpec 并加载 RSA 私钥:

xmlKeyString = "<RSAKeyValue><Modulus>........</D></RSAKeyValue>";
xmlDoc = xmlParse(xmlKeyString);
modBytes = binaryDecode(xmlDoc.RSAKeyValue.Modulus.xmlText, "base64");
dBytes = binaryDecode(xmlDoc.RSAKeyValue.D.xmlText, "base64");
modulus = createObject("java","java.math.BigInteger").init(1, modBytes);
exponent = createObject("java","java.math.BigInteger").init(1, dBytes);
keySpec = createObject("java", "java.security.spec.RSAPrivateKeySpec").init(modulus, exponent);
keyFactory = createObject("java", "java.security.KeyFactory").getInstance("RSA");
privateKey = keyFactory.generatePrivate(keySpec);

选项 2:从 PEM 文件加载密钥:

将 PEM 文件读入变量。删除 header/trailer 即“---BEGIN/END RSA PRIVATE KEY-----”。然后解码 base64 内容并使用 KeyFactory:

加载私钥
rawKey = replace( pemContent, "-----BEGIN RSA PRIVATE KEY-----", "" );
rawKey = replace( rawKey, "-----END RSA PRIVATE KEY-----", "" );
keyBytes = rawKey.trim().binaryDecode("base64");
keySpec = createObject("java", "java.security.spec.PKCS8EncodedKeySpec");
keyFactory = createObject("java", "java.security.KeyFactory").getInstance("RSA");
privateKey = keyFactory.generatePrivate(keySpec.init(keyBytes));

加载私钥后,您可以使用 Signature 对象执行 SHA1 哈希并使用 RSA 密钥生成签名:

stringToSign = "test@membersuite.com";
signer = createObject("java", "java.security.Signature").getInstance("SHA1withRSA");;
signer.initSign(privateKey);
signer.update( stringToSign.getBytes("us-ASCII"));
signedBytes = binaryEncode(signer.sign(), "base64");

writeDump(signedBytes);

结果(使用样本XML):

jTDKoH+INi19kGWn7WRk/PZegLv/9fPUOluaM57x8y1tkuwxOiyX86gxsZ7gU/OsStIT9Q5SVSG5NoaL3B+AxjuLY8b7XBMfTXHv2vidrDkkTTBW0D2LsrkZ3xzmvvPqqfA3tF2HXUYF+zoiTsr3bQdA32CJ+lDNkf+QjV3ZEoc= 

注意: 无论您选择哪种方法,正确保护私钥都非常重要。样本运行后,一定要仔细阅读如何最好地存储和保护私钥。