本文へスキップ
ecdsa.com
C · コマンドライン

NSS (Network Security Services)

NSS は、Firefox と Thunderbird を支える Mozilla の暗号・TLS ライブラリです。署名の層が 2 つあり、ECDSA のエンコーディングが層によって異なります(cryptohi 経由では DER、PK11 経由では生の r‖s)。曲線は P-256 から P-521 までのみで、Ed25519 と ML-KEM にも対応しています。
ライセンス
MPL-2.0
確認したバージョン
3.124 ·
ソースコード
hg.mozilla.org/projects/nss
ドキュメント
firefox-source-docs.mozilla.org/security/nss/index.html

互換性の概要

ECDSAEdDSAポスト量子暗号

ECDSA の曲線
P-256P-384P-521
EdDSA
Ed25519

Network Security Services は、Firefox、Thunderbird、いくつかの Linux システムコンポーネントの基盤となるライブラリで、Firefox の WebCrypto もこの上で動いています。NSS は PKCS#11 を介して動作します。鍵はトークン(組み込みの softoken、またはスマートカードや HSM)の中にあり、その上のすべての層が PK11_* 関数を呼び出します。ここに挙げる事実は、Alpine 3.24.2 のパッケージである NSS 3.124 を鍵データベースなしで初期化して(NSS_NoDB_Init)確認したものです。

ECDSA には、異なるエンコーディングを生成する層が 2 つあります。cryptohi の SEC_SignData と VFY_VerifyData はメッセージをハッシュ化し、X.509、CMS、OpenSSL が想定する形式である DER を使います。PK11_Sign と PK11_Verify はダイジェストを受け取り、生の r‖s 形式(P-256 では 64 バイト)を使います。この 2 つを混在させるのが NSS の典型的な間違いです。DER ⇄ 生形式の変換ツールで変換してください。また、PK11_Sign は P-256 で 64 バイトのダイジェストも受け付け、その先頭 32 バイトに署名したため、曲線に合ったダイジェストを渡してください。

nonce はランダムで(同じメッセージの 2 つの署名は異なりました)、s は正規化されません。high-S の署名も検証を通りました。P-384 の鍵で SHA-256 を使う ECDSA の署名と検証ができたため、ハッシュは曲線に結び付けられていません。失敗の種類は区別できます。VFY_VerifyData が SECFailure を返したあと、PORT_GetError() は、生の署名や途中で切れた署名に対しては SEC_ERROR_BAD_DER を、別のデータに対する有効な署名に対しては SEC_ERROR_BAD_SIGNATURE を報告します。

softoken が鍵を生成できたのは P-256、P-384、P-521 だけで、P-224 と secp256k1 は SEC_ERROR_INVALID_KEY で失敗しました。Ed25519 は CKM_EC_EDWARDS_KEY_PAIR_GEN と CKM_EDDSA で動作しますが、Ed448 は動作しません。ML-KEM-768 のカプセル化とデカプセル化は一通り動作します(PK11_Encapsulate、PK11_Decapsulate)。一方、ヘッダーでは ML-DSA のメカニズムが宣言されていますが、このビルドの softoken はそれを提供していません。KEM は ML-KEM ツールで比較してください。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/nss/run.sh がコンテナイメージ alpine:3.24.2 内で NSS (Network Security Services) 3.124 を実行しました。このページの内容はその出力と一致していなければならず、各事実には該当する出力行を示しています。

  • ECDSA 署名のエンコーディング

    der-or-raw

    SEC_SignData は厳密な DER を返し、PK11_Sign は P-256 で 64 バイトの生の r‖s を返しました。

    チェックによる記録
    fact ecdsa-signature-encoding = der-or-raw
    evidence ecdsa-signature-encoding: SEC_SignData output parses as strict DER: yes; PK11_Sign: 64 bytes (P-256 raw r||s is 64)
  • ECDSA の nonce

    random

    同じメッセージに対する 2 つの SEC_SignData 署名は異なりました。

    チェックによる記録
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: two SEC_SignData signatures of one message are different
  • 署名時の low-S

    either

    s は正規化されません。64 個の署名の中に high-S の値が現れました。

    チェックによる記録
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: signatures with s > n/2 among 64: yes
  • 検証時の high-S

    accepts-high-s

    s を n − s に置き換えた DER 署名も VFY_VerifyData で検証を通りました。

    チェックによる記録
    fact ecdsa-low-s-verify = accepts-high-s
  • 署名呼び出しがハッシュする対象

    hashes-message-or-digest

    SEC_SignData はメッセージをハッシュ化します。メッセージの SHA-256 に対して PK11_Sign で作成した署名は、VFY_VerifyData で受け入れられました。

    チェックによる記録
    fact ecdsa-prehash = hashes-message-or-digest
    evidence ecdsa-prehash: SEC_SignData hashes the message; PK11_Sign over SHA-256(message) verified with VFY_VerifyData: yes
  • 曲線の位数より長いダイジェスト

    truncated

    PK11_Sign は P-256 で 64 バイトのダイジェストを受け付け、その署名は先頭 32 バイトに対して検証を通りました。

    チェックによる記録
    fact ecdsa-long-input = truncated
    evidence ecdsa-long-input: PK11_Sign accepted a 64-byte digest; the signature verifies over its first 32 bytes: yes
  • 曲線に紐付くハッシュ

    none

    P-384 の鍵で、SHA-256 を使う ECDSA の署名と検証ができました。

    チェックによる記録
    fact ecdsa-hash-curve-binding = none
    evidence ecdsa-hash-curve-binding: ECDSA with SHA-256 on a P-384 key: sign ok, verify ok
  • 不正な形式の ECDSA 署名

    returns-error

    VFY_VerifyData は、生の r‖s と 3 バイトのデータに対しては SEC_ERROR_BAD_DER で、誤ったメッセージに対しては SEC_ERROR_BAD_SIGNATURE で失敗しました。

    チェックによる記録
    fact ecdsa-malformed-signature = returns-error
    evidence ecdsa-malformed-signature: VFY_VerifyData returned SECFailure with: raw r||s SEC_ERROR_BAD_DER, 3 bytes SEC_ERROR_BAD_DER, wrong message SEC_ERROR_BAD_SIGNATURE
  • ECDSA の曲線

    P-256P-384P-521

    P-256、P-384、P-521 です。P-224 と secp256k1 の鍵生成は SEC_ERROR_INVALID_KEY を返しました。

    チェックによる記録
    fact ecdsa-curves = P-256, P-384, P-521
    evidence ecdsa-curves: P-224: key generation SEC_ERROR_INVALID_KEY
    evidence ecdsa-curves: secp256k1: key generation SEC_ERROR_INVALID_KEY
  • EdDSA

    Ed25519

    Ed25519 は署名と検証ができ、2 回とも同一の署名を生成しました。Ed448 の鍵生成は SEC_ERROR_INVALID_KEY を返しました。

    チェックによる記録
    fact eddsa = Ed25519
    evidence eddsa: Ed25519: keygen ok, sign and verify ok, deterministic yes
    evidence eddsa: Ed448 (OID 1.3.101.113): SEC_ERROR_INVALID_KEY
  • ポスト量子アルゴリズム

    ML-KEM

    ML-KEM-768 のカプセル化とデカプセル化は 1088 バイトの暗号文で一通り動作しました。ML-DSA-65 の鍵生成は SEC_ERROR_INVALID_ALGORITHM を返し、SLH-DSA は softoken で提供されていません。

    チェックによる記録
    fact pqc = ML-KEM
    evidence pqc: ML-KEM-768: ciphertext 1088 bytes, shared secrets match: yes
    evidence pqc: ML-DSA-65 key generation failed: SEC_ERROR_INVALID_ALGORITHM; CKM_ML_DSA_KEY_PAIR_GEN in the internal slot: no
    evidence pqc: CKM_SLH_DSA in the internal slot: no

使い方

softoken で P-256 の鍵を生成し、`SEC_SignData` で署名して、`VFY_VerifyData` で DER 署名を検証します。
#include <cryptohi.h>
#include <keyhi.h>
#include <nss.h>
#include <pk11pub.h>
#include <secoid.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    NSS_NoDB_Init(NULL);
    /* EC key generation takes the curve OID, DER-encoded, as its parameter. */
    SECOidData *curve = SECOID_FindOIDByTag(SEC_OID_ANSIX962_EC_PRIME256V1);
    unsigned char oid[18] = {0x06, (unsigned char)curve->oid.len};
    memcpy(oid + 2, curve->oid.data, curve->oid.len);
    SECItem params = {siBuffer, oid, curve->oid.len + 2};
    PK11SlotInfo *slot = PK11_GetInternalSlot();
    SECKEYPublicKey *pub = NULL;
    SECKEYPrivateKey *priv = PK11_GenerateKeyPair(slot, CKM_EC_KEY_PAIR_GEN, &params, &pub, PR_FALSE, PR_FALSE, NULL);

    /* SEC_SignData hashes the message and returns DER; PK11_Sign would return raw r||s over a digest. */
    unsigned char msg[] = "order #1042 approved";
    SECItem sig = {siBuffer, NULL, 0};
    SEC_SignData(&sig, msg, sizeof msg - 1, priv, SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
    SECStatus rv = VFY_VerifyData(msg, sizeof msg - 1, pub, &sig, SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, NULL);
    printf("DER tag: 0x%02x, verified: %s\n", sig.data[0], rv == SECSuccess ? "yes" : "no");

    SECITEM_FreeItem(&sig, PR_FALSE);
    SECKEY_DestroyPrivateKey(priv);
    SECKEY_DestroyPublicKey(pub);
    PK11_FreeSlot(slot);
    return NSS_Shutdown() == SECSuccess ? 0 : 1;
}

スニペットのテストハーネスは、すべてのサンプルをコンテナイメージ alpine:3.24.2 内でそのまま実行し、出力が一致しなければ実行は失敗します。

セットアップ
apk add --no-cache -q build-base nss-dev=3.124-r0 >/dev/null
コマンド
cc -o usage usage.c $(pkg-config --cflags --libs nss) && ./usage
一致すべき出力(正規表現)
DER tag: 0x30, verified: yes

ライブラリ