简体   繁体   中英

Dart / Flutter : How Do I Sign a String Using an ES 256 Algorithm and Private Key

Since Flutter doesn't support any map APIs across all platforms (mobile and desktop), I'm trying to fetch map snapshots with Apple's Web Snapshots API . This involves constructing a URL with various options then signing the URL. I append the signature to the end of my request URL so Apple can verify that it's from me.

Apple's instructions state:

To generate a signature, sign the string with your private key using a ES256 algorithm (also known as ECDSA using P-256 curve and SHA-256 hash algorithm). The signature must be Base64 URL-encoded.

I don't need to decrypt anything, I just need to sign the string and add it to the end of my request URL. So I don't think I need anything beyond the crypto library included with Flutter.

Here's what I've tried:

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

I get an unintelligible string as signature and add it to my request URL, but I still get a 401 Not Authorized error, so my signature must be incorrect.

How can I properly sign my URL string with my private key?

Using pointycastle, you need a suitable random number generator instance and a signer initialized with the relevant digest. Then just call generateSignature . That only gets you the r and s values which you need to encode.

Here's an example:

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

A huge thanks to Richard Heap for providing the solution. I just wanted to post the final code I settled on for anyone running into this in the future. Only the basic_utils package is needed.

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

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

//Convert the URL string to Uint8List
final List<int> codeUnits = url.codeUnits;
final Uint8List urlBytes = Uint8List.fromList(codeUnits);

//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);

I just add that signature to the end of the URL string as required by Apple's API and it works great!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM