验证充气城堡上的 javacard 签名 ALG_ECDSA_SHA
Posted
技术标签:
【中文标题】验证充气城堡上的 javacard 签名 ALG_ECDSA_SHA【英文标题】:Verify javacard signature ALG_ECDSA_SHA on bouncy castle 【发布时间】:2015-05-04 18:54:57 【问题描述】:我的问题看起来像这样:我在 javaCard (jcdk 2.2.2) 上生成一个签名,当我想在终端上使用 BouncyCastle 验证它时,签名并不总是验证 - 3,66 中的 1 个(平均 100 次尝试) 签名被验证,其余的返回假。当我验证卡上的签名时,它总是返回 true,但在终端上它通常返回 false,但有时返回 true。因为终端有时会给出肯定的答案,所以我认为代码没问题,原因是其他地方,但我可能是错的。
在 javacard 上使用 Signature.ALG_ECDSA_SHA 和终端 Signature.getInstance("SHA1withECDSA", "BC") 我也尝试过 SHA1withDetECDSA 但我的行为相似。
请帮忙。
【问题讨论】:
看起来很奇怪! 3.66 中的 2.66 对变速箱噪音来说太大了!可以添加程序吗? (生成器和验证器) 请注意,signature 标签不正确,因为它与 method 签名有关。 您的问题解决了吗?如果是,请将其中一个答案标记为已接受。 【参考方案1】:问题在于 JavaCard 和 BouncyCastle 使用不同格式的结果签名。例如,对于 Prime192v1 曲线,生成的 JavaCard 签名始终为 56 字节长,但 Bouncy Castle 签名有时更短,因为它省略了 EC 点坐标中的前导零。
JavaCard 签名(同样适用于 Prime192v1)的十六进制格式如下所示:
30 36 02 19 [25 bytes of the first coord] 02 19 [25 bytes of the second coord]
(是DER编码结构:两个INTEGER的序列)
但是,BouncyCastle 不希望在 EC 坐标中领先这些零。因此,您必须删除它们并修复 DER 结构,例如
30 36 02 19 **00 00** [the rest 23 bytes of the first coord] 02 19 **00** [24 bytes of the second coord]
从 JavaCard 必须为 Bouncy Castle 转换为:
30 **33** 02 **17** [23 bytes of the first coord] 02 **18** [24 bytes of the second coord]
您有时正确验证签名的原因很简单:有时您的 JavaCard 签名坐标中没有前导零。
编辑:受 TajnosAgentos 观察的启发:
BouncyCastle 将坐标编码为有符号整数,JavaCard 始终编码为无符号整数。这就是为什么当第一个字节的最高有效位为 1 时,BouncyCastle 会添加一个特殊的前导零(尽管它会修剪其他前导零),因为坐标始终是正数。
【讨论】:
这是我的密钥签名(未通过验证) 30.__34__.02.__18__.8E.B6.C4.15.9E.A5.9B.4E.45.A1.6B.41.EA .DB.02.BA.75.DF.F7.19.66.DD.BA.D0.02.__18__.BB.BB.DE.3F.82.54.B4.D9.14.D2.28.4D.54.9B.02.5 D.13.48.49.57.C0.44.1E.21。它有 54 个字节长 - 我删除了 SW,所以也许这就是原因。我试图替换标记值.__AA__。不同,但它只适用于 34、18、18。我不知道我做错了什么;( 我的签名看起来像这样 30 34 02 18 [24 bytes] 02 18 [24 byte] [SW - 90 00] PS。 kay 可以(并非总是)使用 makred 值 34-18-18 和 36-18-18 进行验证 你用什么椭圆曲线? X9.62 签名由一个包含两个 INTEGER 值的 ASN.1 SEQUENCE 组成。这些整数值是有符号数,应使用可用的最小八位字节数进行编码。如果你得到一个02
(整数标签),长度字节然后0000
,那么给定签名的encoding肯定是无效的。请注意坐标应始终为正:INTEGER 的最左边位不应为 1(因此 INTEGER 不应以 8x
到 Fx
开头,在这种情况下,INTEGER 应以 00 8x
到 00 Fx
开头)。
我认为我在这方面是一个更好的权威。而且我很确定这是不正确的。它看起来像是根据 BSI 03111 和 SEC1/X9.62 ASN.1 DER 编码将 r 和 s 转换为八位字节字符串的混合体。【参考方案2】:
我有一个解决方案。我不知道为什么会这样,但确实如此... javacard 上的签名函数应该如下所示:
byte[] buffer = apdu.getBuffer();
signature.init(ecPrivateKey, Signature.MODE_SIGN);
short sLen = signature.sign(helloWorld, (short) 0, (short)helloWorld.length, scratch, (short) 0);
if((short)scratch[4] < 0 || (short)scratch[30] < 0)
sign(apdu);
else
Util.arrayCopyNonAtomic(scratch, (short)0, buffer, (short)0, (short) sLen);
apdu.setOutgoingAndSend((short)0, (short)sLen);
是一个“原型”函数。它有效,所以我先分享它,稍后也许我会改进它。 scratch 是在其他地方声明的字节数组。
就像 VOJTA 之前发布的那样,签名(在我的示例中)看起来像这样:
30 34 02 18 [24 字节 NO1] 02 18 [24 字节 NO2] [SW - 90 00]
不需要软件。所以[24 bytes NO1]的第一个字节的SHORT值和[24 bytes NO2]的第一个字节的SHORT值不能为负数。我不知道为什么会这样。我打印了已验证和未验证的签名,我正在寻找任何差异,我发现了这一点。我进行了 1000 多次尝试,验证始终返回 TRUE。这 2 个字节为负数的概率是 1/4((-128,127),0 变为正数,半负数,半正数 -> 1/2 * 1/2 = 1/4)所以可能这就是为什么前面只有 1对 3,66 个签名进行了验证。如果有人知道它为什么起作用,我会很高兴阅读他的帖子
编辑
现在我知道它是如何工作的了。这个问题有解决办法。一种是上面的解决方案——生成签名直到数组的 2 个元素的 SHORT 值为正数,第二种解决方案:
签名看起来像这样:
30 34 02 18 [24 字节 NO1] 02 18 [24 字节 NO2]
现在是 VOJTA 所写的算法(如果需要,粗体值将被更改) lex x=34(符号的第二个字节——这可能会有所不同,在我的签名中是 34)。 如果 [24 byte NO1] 和 [24 byte NO2] 的第一个字节是肯定的,则对签名不做任何事情。 如果 [24 bytes NO1] 的第一个字节的 SHORT 值为负,那么我们必须将 ONE 添加到 X(现在 x=35),将 ONE 添加到 [24 bytes NO1] 的第一个字节旁边的 18 并插入一个签名的 18(现在是 19)旁边的值 00。如果 [24 bytes NO2] 的第一个字节的 SHORT 值为负,那么我们必须将 ONE 添加到 X(现在 x=36),将 ONE 添加到 [24 bytes NO2] 的第一个字节旁边的 18 并插入 00在 18 岁(现在是 19 岁)旁边。如果两个第一个字节都是负数,那么签名应该是这样的:
30 36 02 19 00 [24 字节 NO1] 02 19 00 [24 字节 NO2]
如果只有 [24 bytes NO1] 的第一个字节为负,而 [24 bytes NO2] 的第一个字节为正,则签名应该看起来像这样
30 35 02 19 00 [24 字节 NO1] 02 18 [24 字节 NO2]
最后一种情况: 如果只有 [24 bytes NO1] 的第一个字节为正,而 [24 bytes NO2] 的第一个字节为负,则签名应该看起来像这样
30 35 02 18 [24 字节 NO1] 02 19 00 [24 字节 NO2]
希望对你有用
【讨论】:
整数应该是DER编码的。 DER 使用 signed 整数的最短可能格式。要在 Java 中检索此编码,请使用new BigInteger(1, [24 byte encoding]).toByteArray()
,它将 24 个字节解释为无符号正整数,然后编码为最小有符号表示。否则你可能仍然会失败。以上是关于验证充气城堡上的 javacard 签名 ALG_ECDSA_SHA的主要内容,如果未能解决你的问题,请参考以下文章
验证在 JavaCard 上签名的数据时出现 Java BadPaddingException