如何从 Crypto++ ECDSA 中的签名体中获取签名长度
Posted
技术标签:
【中文标题】如何从 Crypto++ ECDSA 中的签名体中获取签名长度【英文标题】:How to get signature length from signature body in Crypto++ ECDSA 【发布时间】:2019-09-10 14:24:31 【问题描述】:我使用 Crypto++ (libcrypto++ 1.11) 将 JWT 嵌入到我的应用程序中。我使用CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA256>
算法(使用secp256r1
曲线)创建了签名和验证消息的方法。用于验证的令牌可以来自外部世界,所以我需要在知道公钥的情况下验证令牌内容(文本数据)签名。
问题是 Crypto++ 会在无效签名上导致 SegFault,这给我的 Web 服务器带来了很大的痛苦。
我希望 BER 格式的签名(库中的默认序列化格式)具有固定长度,所以我只需将签名的长度与某个常数进行比较。但是,我发现更大的内容可以实现更大的签名,因此需要更深入的方法。
bool ES256Verifier::Verify(const std::string& data,
const std::string& signature)
bool result = false;
try
CryptoPP::StringSource ss(
signature + data, true,
new CryptoPP::SignatureVerificationFilter(
verifier_,
new CryptoPP::ArraySink((byte*)&result, sizeof(result))));
catch (const CryptoPP::BERDecodeErr& err)
LOG_WARNING() << "Signature `" << signature << "` has invalid (non-BER) format";
catch (const CryptoPP::Exception& ex)
LOG_WARNING() << "Signature verification has failed: " << ex.what();
return result;
验证器verifier_
已正确初始化(并成功验证了除了 SegFaults 之外的令牌),但给定 data = ""
和 signature = ""
,例如,我总是得到 SegFault:
__memmove_avx_unaligned_erms 0x00007fb4b9da6b38
CryptoPP::ArraySink::Put2(unsigned char const*, unsigned long, int, bool) 0x00007fb4ba414fb2
CryptoPP::BufferedTransformation::ChannelPut2(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned char const*, unsigned long, int, bool) 0x00007fb4ba3acedc
CryptoPP::StringStore::CopyRangeTo2(CryptoPP::BufferedTransformation&, unsigned long long&, unsigned long long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) const 0x00007fb4ba414e02
CryptoPP::BufferedTransformation::Peek(unsigned char*, unsigned long) const 0x00007fb4ba3ad74a
CryptoPP::Integer::Decode(CryptoPP::BufferedTransformation&, unsigned long, CryptoPP::Integer::Signedness) 0x00007fb4ba45885c
CryptoPP::Integer::Decode(unsigned char const*, unsigned long, CryptoPP::Integer::Signedness) 0x00007fb4ba458c16
CryptoPP::DL_VerifierBase<CryptoPP::ECPPoint>::InputSignature pubkey.h:1560
CryptoPP::SignatureVerificationFilter::LastPut(unsigned char const*, unsigned long) 0x00007fb4ba4159a0
CryptoPP::FilterWithBufferedInput::PutMaybeModifiable(unsigned char*, unsigned long, int, bool, bool) 0x00007fb4ba418107
CryptoPP::BufferedTransformation::ChannelPut2(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned char const*, unsigned long, int, bool) 0x00007fb4ba3acedc
CryptoPP::BufferedTransformation::TransferMessagesTo2(CryptoPP::BufferedTransformation&, unsigned int&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) 0x00007fb4ba3ad8fa
CryptoPP::BufferedTransformation::TransferAllTo2(CryptoPP::BufferedTransformation&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) 0x00007fb4ba3adb21
CryptoPP::SourceTemplate<CryptoPP::StringStore>::PumpAll2 filters.h:1238
CryptoPP::Source::PumpAll filters.h:1182
CryptoPP::Source::SourceInitialize filters.h:1215
CryptoPP::StringSource::StringSource filters.h:1271
jwt::signature::algorithm::ES256Verifier::Verify es256_verifier.cpp:40
ES256_SignatureTest_Test::TestBody es256_test.cpp:29
...
那么,有没有办法查看数据和签名,并确定此特定组合是否会因签名长度无效而导致 SegFault?
【问题讨论】:
我使用的样本取自wiki,不,SignatureVerificationFilter 不会改变任何东西。 看起来它可能放置了多个字节,尝试在CryptoPP::ArraySink::Put2
上放置一个断点,看看它被调用了多少次以及它试图写入多少字节?
如果您提供Minimal, Complete, and Verifiable example,那么我可能会提供一个彻底的答案。我猜您正在使用类似 ElGamal 的方案,因此签名的 [二进制] 大小为 2*field-element-size,因为存在 r
和 s
。 "r and s" 附近也有一些可变性,因为它可以是 r || s
的简单串联,也可以是 DER 编码。
Crypto++ wiki 上的 Elliptic Curve Digital Signature Algorithm | OpenSSL and Java Interop 提供了两种呈现 r
和 s
的方法示例。它显示了这两种格式,因为必须将签名从 DER 转换为 P1363 才能使用 Crypto++。
@jww 对不起,我将在周日制作 MCVE,目前它嵌入在自定义代码库中。似乎在 DL_VerifierBase 中无论如何都读取了 32 个字节而没有任何检查,因此检查签名是否至少为 32 个字节可以解决特定的“”/“”问题。周日我会在开启 gdb 的情况下做几个实验。
【参考方案1】:
下面是一些使用字段元素、签名者和验证者确定签名长度的示例代码。第一个输出打印元素长度和r||s
长度,因为r||s
是P1363 格式的签名。
第二个和第三个输出只是打印SignatureLength()
的结果。您的程序应该拒绝比SignatureLength()
更短的签名。甚至试图验证一个简短的签名也是没有意义的,因为它不好。
注意:这仅适用于DL_*
签名方案(基于离散日志)。它不适用于TF_*
签名方案(基于陷门函数)。
#include "cryptlib.h"
#include "eccrypto.h"
#include "osrng.h"
#include "oids.h"
#include <iostream>
int main(int argc, char* argv[])
using namespace CryptoPP;
AutoSeededRandomPool prng;
///// Element
DL_GroupParameters_EC<ECP> params(ASN1::secp256r1());
unsigned int elemLength = params.GetCurve().GetField().MaxElementByteLength();
std::cout << "Element length: " << elemLength << std::endl;
std::cout << "r||s length: " << 2*elemLength << std::endl;
///// Signer
ECDSA<ECP, SHA256>::Signer signer;
signer.AccessKey().Initialize(prng, params);
unsigned int signerLength = signer.SignatureLength();
std::cout << "Signer signature length: " << signerLength << std::endl;
///// Verifier
ECDSA<ECP, SHA256>::Verifier verifier(signer);
unsigned int verifierLength = verifier.SignatureLength();
std::cout << "Verifier signature length: " << verifierLength << std::endl;
return 0;
运行程序会产生以下结果。
$ ./test.exe
Element length: 32
r||s length: 64
Signer signature length: 64
Verifier signature length: 64
如果您将曲线切换到ASN1::secp521r1()
,则运行程序会产生以下结果。
$ ./test.exe
Element length: 66
r||s length: 132
Signer signature length: 132
Verifier signature length: 132
【讨论】:
以上是关于如何从 Crypto++ ECDSA 中的签名体中获取签名长度的主要内容,如果未能解决你的问题,请参考以下文章
使用 ECDSA 签名时的 BAD_ACCESS (code=EXC_I386_GPFLT)
如何从 R 中的散列消息和签名中正确恢复 ECDSA 公钥 ||小号 || V格式?