Guomi.SM2 (guomi v0.5.3)

Copy Markdown View Source

Pure Elixir SM2 cryptographic operations (GM/T 0003-2012).

SM2 is a Chinese commercial cryptographic algorithm standard for:

  • Key pair generation (ECDH)
  • Digital signature (ECDSA with SM3 pre-hash)
  • Encryption/decryption (ECDH + SM3 KDF + XOR + SM3 MAC)

This is a pure Elixir implementation with no external dependencies.

The explicit sign_standard/3, verify_standard/4, encrypt_standard/2, and decrypt_standard/2 APIs implement ZA-aware signatures and the standard SM3 KDF. The shorter legacy APIs retain the historical Guomi-specific format; legacy encryption repeats a 32-byte XOR mask and must not protect sensitive data or be used as part of a production protocol.

Summary

Functions

Decrypts the legacy Guomi-specific compatibility ciphertext format.

Decrypts standard raw SM2 C1 || C3 || C2 ciphertext.

Encrypts with the legacy Guomi-specific C1 || C2 || C3 compatibility format.

Encrypts a non-empty message using the standard SM2 KDF and C1 || C3 || C2.

Generates an SM2 key pair.

Signs message with a 32-byte private key and returns raw r || s bytes.

Produces a standards-compatible raw SM2 signature using an explicit user ID.

Returns true; the current SM2 primitives are implemented entirely in Elixir.

Calculates the SM2 user identity digest ZA for user_id and public_key.

Verifies a raw 64-byte r || s compatibility signature.

Verifies a standards-compatible raw SM2 signature using the same user ID.

Types

error_reason()

@type error_reason() ::
  :invalid_key
  | :invalid_input
  | :invalid_signature
  | :invalid_ciphertext
  | :decryption_failed

Functions

decrypt(ciphertext, private_key)

@spec decrypt(binary(), binary()) :: {:ok, binary()} | {:error, error_reason()}

Decrypts the legacy Guomi-specific compatibility ciphertext format.

Returns :invalid_ciphertext for malformed framing, :invalid_key for an invalid private key, or :decryption_failed when authentication fails.

decrypt_standard(ciphertext, private_key)

@spec decrypt_standard(binary(), binary()) ::
  {:ok, binary()} | {:error, error_reason()}

Decrypts standard raw SM2 C1 || C3 || C2 ciphertext.

This function never falls back to the legacy Guomi format. Integrity failure, an invalid ephemeral point, or an all-zero KDF returns :decryption_failed.

encrypt(plaintext, public_key)

@spec encrypt(binary() | iodata(), binary()) ::
  {:ok, binary()} | {:error, error_reason()}

Encrypts with the legacy Guomi-specific C1 || C2 || C3 compatibility format.

This format repeats a 32-byte XOR mask for longer messages and must not be used for sensitive data or production protocols. Invalid iodata returns {:error, :invalid_input}.

encrypt_standard(plaintext, public_key)

@spec encrypt_standard(binary() | iodata(), binary()) ::
  {:ok, binary()} | {:error, error_reason()}

Encrypts a non-empty message using the standard SM2 KDF and C1 || C3 || C2.

C1 is a 65-byte uncompressed point, C3 is the 32-byte SM3 integrity digest, and C2 has the same length as the plaintext. This API has not undergone an independent security audit.

generate_keypair()

@spec generate_keypair() :: {:ok, binary(), binary()}

Generates an SM2 key pair.

The private key is a 32-byte big-endian integer. The public key is a 65-byte uncompressed point encoded as 0x04 || x || y.

sign(message, private_key)

@spec sign(binary() | iodata(), binary()) ::
  {:ok, binary()} | {:error, error_reason()}

Signs message with a 32-byte private key and returns raw r || s bytes.

This compatibility API signs SM3(message) and does not calculate the SM2 user identity digest ZA. Do not assume interoperability with standard SM2 signing APIs. Invalid iodata returns {:error, :invalid_input}.

sign_standard(message, private_key, user_id)

@spec sign_standard(binary() | iodata(), binary(), binary()) ::
  {:ok, binary()} | {:error, error_reason()}

Produces a standards-compatible raw SM2 signature using an explicit user ID.

The signed digest is SM3(ZA || message). The result is 64-byte raw r || s; DER encoding is intentionally not implicit.

supported?()

@spec supported?() :: boolean()

Returns true; the current SM2 primitives are implemented entirely in Elixir.

user_identity_digest(user_id, public_key)

@spec user_identity_digest(binary(), binary()) ::
  {:ok, binary()} | {:error, error_reason()}

Calculates the SM2 user identity digest ZA for user_id and public_key.

The user ID must be a binary no longer than 8191 bytes so its bit length fits the standard 16-bit ENTLA field.

verify(message, signature, public_key)

@spec verify(binary() | iodata(), binary(), binary()) ::
  {:ok, boolean()} | {:error, error_reason()}

Verifies a raw 64-byte r || s compatibility signature.

Returns {:ok, false} for a well-formed but invalid signature and an error for malformed keys, signatures, or message input. This API does not calculate ZA.

verify_standard(message, signature, public_key, user_id)

@spec verify_standard(binary() | iodata(), binary(), binary(), binary()) ::
  {:ok, boolean()} | {:error, error_reason()}

Verifies a standards-compatible raw SM2 signature using the same user ID.

Returns {:ok, false} when the inputs are well formed but the signature or user ID does not match.