【问题标题】:Dart / Flutter : How Do I Sign a String Using an ES 256 Algorithm and Private KeyDart / Flutter:如何使用 ES 256 算法和私钥对字符串进行签名
【发布时间】:2022-09-29 00:31:56
【问题描述】:

由于 Flutter 不支持所有平台(移动和桌面)的任何地图 API,我正在尝试使用 Apple 的 Web Snapshots API 获取地图快照。这涉及使用各种选项构建 URL,然后对 URL 进行签名。我将签名附加到我的请求 URL 的末尾,以便 Apple 可以验证它来自我。

苹果的说明书状态:

要生成签名,请使用 ES256 算法(也称为 ECDSA,使用 P-256 曲线和 SHA-256 哈希算法)使用您的私钥对字符串进行签名。签名必须是 Base64 URL 编码的。

我不需要解密任何东西,我只需要对字符串进行签名并将其添加到我的请求 URL 的末尾。所以我认为我不需要 Flutter 附带的 crypto 库之外的任何东西。

这是我尝试过的:

import \'package:crypto/crypto.dart\';

//Private Key
var key = utf8.encode(\'\'\'
-----BEGIN PRIVATE KEY-----
abcdef...
-----END PRIVATE KEY-----
\'\'\');

var bytes = utf8.encode(\'My URL String to Sign...\');

var hmacSha256 = Hmac(sha256, key);
var sig = hmacSha256.convert(bytes);
    
var signature = base64UrlEncode(sig.bytes);

我得到一个难以理解的字符串 signature 并将其添加到我的请求 URL,但我仍然得到一个 401 Not Authorized 错误,所以我的签名一定是不正确的。

如何使用我的私钥正确签署我的 URL 字符串?

  • 发布的代码使用与指定(ECDSA = 椭圆曲线 DSA)完全不同的算法 (HMAC)。您需要一个支持 ECDSA 的库,例如尖角城堡。

标签: flutter dart digital-signature


【解决方案1】:

使用 pointycastle,您需要一个合适的随机数生成器实例和一个使用相关摘要初始化的签名者。然后拨打generateSignature。这只会让你得到你需要编码的rs 值。

这是一个例子:

import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';

import 'package:pointycastle/asn1.dart';
import 'package:pointycastle/export.dart';

  // the private key
  ECPrivateKey? privateKey;
  
  // some bytes to sign
  final bytes = Uint8List(0);

  // a suitable random number generator - create it just once and reuse
  final rand = Random.secure();
  final fortunaPrng = FortunaRandom()
    ..seed(KeyParameter(Uint8List.fromList(List<int>.generate(
      32,
      (_) => rand.nextInt(256),
    ))));

  // the ECDSA signer using SHA-256
  final signer = ECDSASigner(SHA256Digest())
    ..init(
      true,
      ParametersWithRandom(
        PrivateKeyParameter(privateKey!),
        fortunaPrng,
      ),
    );

  // sign the bytes
  final ecSignature = signer.generateSignature(bytes) as ECSignature;

  // encode the two signature values in a common format
  // hopefully this is what the server expects
  final encoded = ASN1Sequence(elements: [
    ASN1Integer(ecSignature.r),
    ASN1Integer(ecSignature.s),
  ]).encode();

  // and finally base 64 encode it
  final signature = base64UrlEncode(encoded);

【讨论】:

  • 哇谢谢你。一百万年里我不可能自己弄清楚这一点。 ? 我不清楚如何将我的私钥(一个包含字符串的 .p8 文件)转换为 ECPrivateKey,但你给了我很大的帮助。再次感谢!
  • 使用basic_utils 包。见:pub.dev/packages/basic_utils#cryptoutils 有一个方法ECPrivateKey ecPrivateKeyFromPem(String pem)。 (我看到有一些我以前没有见过的新实用程序方法:ecSignecSignatureToBase64,它们执行上面的大部分代码。)
  • 完美,谢谢!
【解决方案2】:

非常感谢理查德·希普用于提供解决方案。我只是想为将来遇到此问题的任何人发布我确定的最终代码。只需要the basic_utils package

import 'package:basic_utils/basic_utils.dart';
import 'dart:typed_data';

final url = 'My URL string...';

//Convert the URL string to Uint8List
final urlBytes = utf8.encode(url) as Uint8List;

//Prep the private key
var key = '''
-----BEGIN PRIVATE KEY-----
abcdef...
-----END PRIVATE KEY-----
''');

ECPrivateKey privateKey = CryptoUtils.ecPrivateKeyFromPem(key);

//Sign the URL
ECSignature sig = CryptoUtils.ecSign(privateKey, urlBytes, algorithmName: 'SHA-256/ECDSA');

//Convert signature to Base64
final signature = CryptoUtils.ecSignatureToBase64(sig);

我只是按照 Apple API 的要求将 signature 添加到 URL 字符串的末尾,效果很好!

【讨论】:

  • 最好是:final urlBytes = utf8.encode(url) as Uint8List;
猜你喜欢
  • 2019-03-13
  • 2023-04-02
  • 1970-01-01
  • 2015-01-25
  • 1970-01-01
  • 2013-06-06
  • 2011-03-11
  • 2014-11-12
  • 2023-01-29
相关资源
最近更新 更多