4

When I try to make a request I get the following error message:

code: -1100, 
msg: Illegal characters found in parameter 'signature'; legal range is '^[A-Fa-f0-9]{64}$'

Link to binance API: https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md

The request is being made to https://api.binance.com/api/v3/account. The query parameter is only the timestamp, since it's required.

I'm 100% sure there's something wrong with the way I'm signing the message. I'm sure because it includes characters like '+-/_=', which aren't allowed apparently. The mistake must be somewhere in the middle section of the code, but I can't seem to figure it out.

I've already looked through the following sites:

String baseUrl = 'https://api.binance.com/api/v3/account';
int timeStamp = DateTime.now().millisecondsSinceEpoch;
String queryParams = 'timestamp=' + timeStamp.toString();
String secret = 'SECRET_KEY_HERE';

List<int> messageBytes = utf8.encode(queryParams);
List<int> key = base64.decode(secret);
Hmac hmac = new Hmac(sha256, key);
Digest digest = hmac.convert(messageBytes);
String signature = base64.encode(digest.bytes);
String url = baseUrl + "?" + "signature=" + signature + "&" + queryParams;

var response = await http.get(
    url,
    headers: {
      "Accept": "application/json",
      "HTTP_ACCEPT_LANGUAGE": "en-US",
      "Accept-Language": "en-US",
      "X-MBX-APIKEY": "API_KEY_HERE"
    }
);

print(response.body);

EDIT - Working signature

String baseUrl = 'https://api.binance.com/api/v3/account?';
int timeStamp = DateTime.now().millisecondsSinceEpoch;
String queryParams = 'recvWindow=5000' + '&timestamp=' + timeStamp.toString();
String secret = 'SECRET_KEY_HERE';

List<int> messageBytes = utf8.encode(queryParams);
List<int> key = utf8.encode(secret);
Hmac hmac = new Hmac(sha256, key);
Digest digest = hmac.convert(messageBytes);
String signature = hex.encode(digest.bytes);
String url = baseUrl + queryParams + "&signature=" + signature;

var response = await http.get(
  url,
  headers: {
    "Accept": "application/json",
    "HTTP_ACCEPT_LANGUAGE": "en-US",
    "Accept-Language": "en-US",
    "X-MBX-APIKEY": "API_KEY_HERE"
  }
);

print(response.body);
RazerBoy
  • 43
  • 5
  • '+-/' is one variant of base64 , you may want to use URL-safe variant https://en.wikipedia.org/wiki/Base64#URL_applications using https://api.dartlang.org/stable/1.24.3/dart-convert/Base64Codec/Base64Codec.urlSafe.html – TruongSinh Mar 23 '19 at 01:49
  • Hello and thank you for your reply, I can see your point. I've tried doing this `Base64Codec.urlSafe().encode(digest.bytes)` instead of this `base64.encode(digest.bytes)` and `Base64Codec.urlSafe().decode(secret)` instead of `base64.decode(secret)`, but it does not work. The Base64Codec still returns unwanted charachters like _. – RazerBoy Mar 23 '19 at 03:22
  • I've also tried using `base64Url.decode(secret)` with `base64Url.encode(digest.bytes)`, `base64Decode(secret)` with `base64Encode(digest.bytes)`, `base64.decode(secret)` with `base64UrlEncode(digest.bytes)`, `base64.encode(digest.bytes)` with `Base64Encoder.urlSafe().convert(digest.bytes)` and `Base64Decoder().convert(secret)` with `Base64Encoder(digest.bytes).convert()`. None of them work. And that's all of the Base64 and base64 encodings/decodins I can find. The problem is always the same – RazerBoy Mar 23 '19 at 03:29
  • I've also noticed that with every encoder/decoder I use the last char is =, which is also a problem since = is not an allowed character – RazerBoy Mar 23 '19 at 03:31
  • I found this https://api.dartlang.org/stable/2.2.0/dart-core/Uri/encodeComponent.html. The method `encodeComponent()` does a percent-encoding. But this still does not work because % is also an unwanted character. – RazerBoy Mar 23 '19 at 04:20
  • It may be good to list the required imports for your working solution: `import 'dart:convert'; import 'package:crypto/crypto.dart'; import "package:hex/hex.dart"; import 'package:http/http.dart' as http;` – markus Nov 12 '21 at 13:55

2 Answers2

0

Your API requires that the signature be presented in hex - hence the regular expression '^[A-Fa-f0-9]{64}$' - i.e. 64 hex upper or lower case characters.

Instead of base64.encode(digest.bytes), which converts the bytes to base 64, convert the bytes to hex using the convert package. NOTE: this is not the dart:convert library. It's a pub package, so you have to add it to pubspec.yaml and import it.

Then you can use hex.encode(digest.bytes).

Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • Hello and thank you for your reply. I've tried `hex.encode(digest.bytes)` and I do believe it works. I'm still having problems with `code: -1022, msg: Signature for this request is not valid`. But when I resolve this I'll mark your answer as the correct one. Since I do believe your answer is correct. Thank you again kind stranger. – RazerBoy Mar 24 '19 at 01:28
  • Yes, it does work. I just had to change `String signature = base64.encode(digest.bytes);` to `String signature = hex.encode(digest.bytes);`. But I also had to change `List key = base64.decode(secret);` to `List key = utf8.encode(secret);`. Again, thank you for your help! I'll add the working code to my question and mark your answer as the correct one. – RazerBoy Mar 24 '19 at 02:05
0

I again get error: {"code":-1022,"msg":"Signature for this request is not valid."}

import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart'
as http;
import 'package:convert/convert.dart';

class Binance {
  static void test() async {
    String baseUrl = 'api.binance.com';
    String path = '/api/v3/account';
    int timeStamp = DateTime.now().millisecondsSinceEpoch;
    String queryParams =
      '?recvWindow=5000' + '&timestamp=' + timeStamp.toString();
    String secret =
      'SECRET_KEY_HERE';
    List < int > messageBytes = utf8.encode(queryParams);
    List < int > key = utf8.encode(secret);
    Hmac hmac = new Hmac(sha256, key);
    Digest digest = hmac.convert(messageBytes);
    String signature = hex.encode(digest.bytes);
    Map < String, dynamic > parameters = {
      'recvWindow': '5000',
      'timestamp': timeStamp.toString(),
      'signature': signature,
    };
    Uri uri = Uri.https(baseUrl, path, parameters);
    var response = await http.get(uri, headers: {
      "Accept": "application/json",
      "HTTP_ACCEPT_LANGUAGE": "en-US",
      "Accept-Language": "en-US",
      "X-MBX-APIKEY": "API_KEY_HERE"
    });

    print(response.body);
  }
}