バイナンス(Binance)API署名の標準アルゴリズムは HMAC-SHA256 です。その仕組みは、シークレットキー(Secret Key)を鍵、クエリ文字列をメッセージとして、64文字の16進数文字列を生成し、それを signature パラメータとしてリクエストの末尾に付加するというものです。2023年からは、よりセキュリティの高い Ed25519 非対称署名スキームも導入されました。この記事では、アルゴリズムの原理、5つの言語での実装、および8つの一般的な署名エラーの解決方法を詳しく解説します。バイナンスアカウントをお持ちでない方は、まず バイナンス公式サイト で登録を完了させてください。アカウントがない方は 無料登録 も可能です。
一、HMAC-SHA256 の数学的原理
HMAC(Hash-based Message Authentication Code)は、ハッシュ関数に基づいたメッセージ認証コードであり、以下の公式で表されます:
HMAC(K, m) = H((K ⊕ opad) || H((K ⊕ ipad) || m))
ここで:
- K は鍵(シークレットキー)
- m はメッセージ(クエリ文字列)
- H は SHA256 ハッシュ関数
- ipad = 0x36 を64回繰り返したもの
- opad = 0x5C を64回繰り返したもの
- ⊕ は排他的論理和(XOR)、|| は結合を意味します
バイナンスでの具体的なプロセス:
- すべてのパラメータを順番に
key1=value1&key2=value2の形式で結合します。 - シークレットキーを HMAC の鍵、上記の文字列をメッセージとして使用します。
- SHA256 ダイジェストを計算し、32バイトを出力します。
- これを64文字の16進数文字列に変換します。
二、バイナンス署名の3つのパラメータ規則
規則 1:パラメータの順序は署名時とリクエスト時で一致させる
# 正解:署名に使用した文字列とリクエストの query が一致している
params = {"symbol": "BTCUSDT", "timestamp": 1713027384562}
signature = hmac_sha256("symbol=BTCUSDT×tamp=1713027384562")
final_query = "symbol=BTCUSDT×tamp=1713027384562&signature=" + signature
# 不正解:署名に使用した順序と final_query の順序が異なると、-1022 エラーになります。
規則 2:signature フィールド自体は署名対象に含めない
# signature フィールドは常に最後に配置し、HMAC 計算には含めません。
params = {"symbol": "BTCUSDT", "timestamp": 1713027384562}
sig = hmac_sha256(urlencode(params)) # まず署名を計算
params["signature"] = sig # その後で追加する
規則 3:数値に引用符を付けない、文字列に空白を入れない
# 正しい形式
timestamp=1713027384562
# 誤った形式(署名の結果が変わってしまいます)
timestamp="1713027384562"
timestamp= 1713027384562
三、Python による標準実装
import hmac
import hashlib
from urllib.parse import urlencode
SECRET_KEY = "YOUR_SECRET_KEY"
def sign(params: dict) -> str:
"""標準的な署名実装"""
query = urlencode(params)
return hmac.new(
SECRET_KEY.encode('utf-8'),
query.encode('utf-8'),
hashlib.sha256
).hexdigest()
# テスト
params = {
"symbol": "BTCUSDT",
"side": "BUY",
"type": "LIMIT",
"timeInForce": "GTC",
"quantity": "0.001",
"price": "60000.00",
"timestamp": 1713027384562
}
print(sign(params))
# 出力例: b42e1fa3d8c7e9f2a6b5c4d1e8f9a0b3c2d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9
四、Node.js による標準実装
const crypto = require('crypto');
const SECRET_KEY = 'YOUR_SECRET_KEY';
function sign(params) {
// URLSearchParams は自動的にエンコードを処理します
const query = new URLSearchParams(params).toString();
return crypto
.createHmac('sha256', SECRET_KEY)
.update(query)
.digest('hex');
}
const params = {
symbol: 'BTCUSDT',
side: 'BUY',
type: 'LIMIT',
timeInForce: 'GTC',
quantity: '0.001',
price: '60000.00',
timestamp: Date.now()
};
console.log(sign(params));
注意:Node.js の JSON.stringify で得られるのは JSON 文字列であり、署名には使用できません。必ず URLSearchParams を使用するか、手動で結合してください。
五、Go による標準実装
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/url"
)
func sign(params url.Values, secret string) string {
query := params.Encode()
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(query))
return hex.EncodeToString(h.Sum(nil))
}
func main() {
params := url.Values{}
params.Set("symbol", "BTCUSDT")
params.Set("side", "BUY")
params.Set("type", "LIMIT")
params.Set("timeInForce", "GTC")
params.Set("quantity", "0.001")
params.Set("price", "60000.00")
params.Set("timestamp", "1713027384562")
fmt.Println(sign(params, "YOUR_SECRET_KEY"))
}
重要:url.Values.Encode() は自動的にアルファベット順にソートし、URLエンコードを行います。これは Python の urlencode と一致した出力を提供します。
六、Rust による実装
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
fn sign(query: &str, secret: &str) -> String {
let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
.expect("HMAC init error");
mac.update(query.as_bytes());
hex::encode(mac.finalize().into_bytes())
}
fn main() {
let query = "symbol=BTCUSDT&side=BUY&type=LIMIT&timeInForce=GTC&quantity=0.001&price=60000.00×tamp=1713027384562";
let secret = "YOUR_SECRET_KEY";
println!("{}", sign(query, secret));
}
Cargo.toml の設定:
[dependencies]
hmac = "0.12"
sha2 = "0.10"
hex = "0.4"
七、Java による実装
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class BinanceSigner {
public static String sign(String query, String secret) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(
secret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(keySpec);
byte[] hash = mac.doFinal(query.getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : hash) {
hex.append(String.format("%02x", b));
}
return hex.toString();
}
public static void main(String[] args) throws Exception {
String query = "symbol=BTCUSDT×tamp=1713027384562";
String secret = "YOUR_SECRET_KEY";
System.out.println(sign(query, secret));
}
}
八、Ed25519 による代替案(新規)
2023年、バイナンスは Self-generated API Key を導入し、Ed25519 非対称署名の使用を可能にしました。利点:シークレット(秘密鍵)がローカル環境から外に出ることはありません。
1. 鍵ペアの生成
openssl genpkey -algorithm Ed25519 -out private.pem
openssl pkey -in private.pem -pubout -out public.pem
# public.pem をバイナンスの API 管理ページにアップロードします
2. Python による Ed25519 署名
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
import base64
with open("private.pem", "rb") as f:
private_key = load_pem_private_key(f.read(), password=None)
def ed25519_sign(query: str) -> str:
signature_bytes = private_key.sign(query.encode())
return base64.b64encode(signature_bytes).decode()
# 使用方法は HMAC と似ていますが、署名結果は hex ではなく base64 になります
query = "symbol=BTCUSDT×tamp=1713027384562"
sig = ed25519_sign(query)
final_url = f"/api/v3/order?{query}&signature={sig}"
九、署名エラーのチェックリスト
-1022 Signature for this request is not valid エラーが発生した場合は、以下の項目を順番に確認してください:
| # | 項目 | 検証方法 |
|---|---|---|
| 1 | シークレットキーの余計な空白/改行 | len(secret) を出力し、64文字であることを確認 |
| 2 | パラメータの順序不一致 | 署名に使用した文字列と実際の query 文字列を比較 |
| 3 | signature が HMAC に含まれている | 署名を計算してからフィールドを追加しているか確認 |
| 4 | URL エンコード方式の差異 | @ を %40 とするかなど、署名とリクエストで一貫させる |
| 5 | 数値に引用符を付けている | timestamp="123" と timestamp=123 では署名結果が異なります |
| 6 | 文字コードのエラー | 非ASCII文字は必ず UTF-8 でエンコードする |
| 7 | 鍵タイプの混同 | HMAC キーには HMAC アルゴリズム、Ed25519 キーには Ed25519 を使用 |
| 8 | POST body vs query string | パラメータは body または URL のどちらか一方に配置する(重複は不可) |
十、署名デバッグ用ツールコード
def debug_sign(params, secret):
"""各ステップを詳細に出力"""
query = urlencode(params)
print(f"Step 1. クエリ文字列: {query}")
print(f" 長さ: {len(query)} バイト")
sig = hmac.new(secret.encode(), query.encode(), hashlib.sha256).hexdigest()
print(f"Step 2. 署名: {sig}")
print(f" 長さ: {len(sig)} 文字(64文字であるべき)")
final = f"{query}&signature={sig}"
print(f"Step 3. 最終 URL: {final}")
return sig
debug_sign({"symbol": "BTCUSDT", "timestamp": 1713027384562}, "YOUR_SECRET")
十一、よくある質問 FAQ
Q1: 同じパラメータでも署名の結果が毎回同じになるのはなぜですか?
A: HMAC-SHA256 は決定論的なアルゴリズムであり、同じ入力に対しては常に同じ出力を返します。そのため、リプレイ攻撃を防ぐために時間戳(timestamp)をミリ秒単位で変化させ、毎回異なる署名が生成されるようにする必要があります。
Q2: 署名からシークレットキーが特定されることはありますか?
A: いいえ、ありません。SHA-256 は一方通行の関数であり、HMAC 値から鍵を逆算することは数学的にほぼ不可能です(約 2^128 回の計算が必要)。シークレットの流出は、管理上のミスによるものがほとんどです。
Q3: signature はなぜ 64 文字なのですか?
A: SHA-256 の出力は 256ビット = 32バイト です。1バイトを2文字の16進数で表すため、合計で 64文字 になります。これ以外の長さになる場合は実装に誤りがあります。
Q4: タイムスタンプにマイクロ秒(microseconds)を使用できますか?
A: できません。バイナンスはミリ秒(13桁の数字)を要求しています。16桁のマイクロ秒を使用すると、数十年後の未来の時間と認識され、-1021 Timestamp out of recv window エラーが発生します。
Q5: Ed25519 と HMAC-SHA256、どちらが安全ですか?
A: Ed25519 の方が安全です。秘密鍵を一切送信せず、バイナンス側には公開鍵のみを渡すため、万が一バイナンスのサーバーが侵害されても秘密鍵は漏洩しません。ただし、Ed25519 は SDK のサポートが HMAC ほど成熟していないため、一般的な取引戦略であれば HMAC で十分と言えます(シークレットをローカルで暗号化保存する場合)。
署名の原理を理解したら、カテゴリナビ に戻って「API連携」カテゴリの他の技術トピックをチェックしましょう。