AwsEncryptionSdk.Crypto.ECDSA (AWS Encryption SDK v1.2.2)

View Source

ECDSA operations for AWS Encryption SDK.

Provides key generation, signing, verification and key encoding for the curves of the signed algorithm suites: P-256 (secp256r1, with SHA-256) for 0x0214, and P-384 (secp384r1, with SHA-384) for 0x0346, 0x0378 and 0x0578. curve_for_suite/1 maps a suite to its curve and hash_for_curve/1 a curve to its hash.

Spec Reference

https://github.com/awslabs/aws-encryption-sdk-specification/blob/master/framework/algorithm-suites.md

Summary

Functions

Returns the curve a signed algorithm suite signs on.

Decodes a base64-encoded public key from encryption context.

Encodes a public key to base64 for storage in encryption context.

Generates an ECDSA key pair for the given curve (:secp256r1 or :secp384r1).

Returns the hash a curve signs with: SHA-256 on P-256, SHA-384 on P-384.

Normalizes a public key to uncompressed format.

Generates an ECDSA signature over the given message, with the curve's hash (SHA-256 on P-256, SHA-384 on P-384).

Signs a pre-computed digest using ECDSA.

Verifies an ECDSA signature over the given message, with the curve's hash (SHA-256 on P-256, SHA-384 on P-384).

Verifies an ECDSA signature against a pre-computed digest.

Types

curve()

@type curve() :: :secp384r1 | :secp256r1

key_pair()

@type key_pair() :: {private_key :: binary(), public_key :: binary()}

Functions

curve_for_suite(map)

@spec curve_for_suite(AwsEncryptionSdk.AlgorithmSuite.t()) :: curve()

Returns the curve a signed algorithm suite signs on.

:ecdsa_p256 (suite 0x0214) signs on :secp256r1; :ecdsa_p384 (suites 0x0346, 0x0378 and 0x0578) on :secp384r1.

Examples

iex> suite = AwsEncryptionSdk.AlgorithmSuite.aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256()
iex> AwsEncryptionSdk.Crypto.ECDSA.curve_for_suite(suite)
:secp256r1

iex> suite = AwsEncryptionSdk.AlgorithmSuite.aes_256_gcm_hkdf_sha512_commit_key_ecdsa_p384()
iex> AwsEncryptionSdk.Crypto.ECDSA.curve_for_suite(suite)
:secp384r1

decode_public_key(encoded)

@spec decode_public_key(String.t()) :: {:ok, binary()} | {:error, :invalid_base64}

Decodes a base64-encoded public key from encryption context.

Examples

iex> {_private_key, public_key} = AwsEncryptionSdk.Crypto.ECDSA.generate_key_pair(:secp384r1)
iex> encoded = AwsEncryptionSdk.Crypto.ECDSA.encode_public_key(public_key)
iex> {:ok, decoded} = AwsEncryptionSdk.Crypto.ECDSA.decode_public_key(encoded)
iex> AwsEncryptionSdk.Crypto.ECDSA.normalize_public_key(decoded, :secp384r1) == public_key
true

encode_public_key(public_key)

@spec encode_public_key(binary()) :: String.t()

Encodes a public key to base64 for storage in encryption context.

An uncompressed point (0x04 prefix) is written as the point compressed according to SEC 1 v2.0 section 2.3.3 (a 0x02 or 0x03 prefix by the parity of y, followed by x), as the specification's framework/transitive-requirements.md requires for a serialized verification key, then base64 encoded. Any other binary (a point already compressed) is base64 encoded as it is. Versions before 1.1.0 wrote the uncompressed point; decode_public_key/1 and normalize_public_key/2 still read both forms.

Examples

iex> {_private_key, public_key} = AwsEncryptionSdk.Crypto.ECDSA.generate_key_pair(:secp384r1)
iex> encoded = AwsEncryptionSdk.Crypto.ECDSA.encode_public_key(public_key)
iex> byte_size(Base.decode64!(encoded))
49

generate_key_pair(curve)

@spec generate_key_pair(curve()) :: key_pair()

Generates an ECDSA key pair for the given curve (:secp256r1 or :secp384r1).

Returns {private_key, public_key} where:

  • private_key is the raw private key bytes
  • public_key is the uncompressed public key point

Examples

iex> {private_key, public_key} = AwsEncryptionSdk.Crypto.ECDSA.generate_key_pair(:secp384r1)
iex> byte_size(private_key)
48
iex> byte_size(public_key)
97

iex> {_private_key, public_key} = AwsEncryptionSdk.Crypto.ECDSA.generate_key_pair(:secp256r1)
iex> byte_size(public_key)
65

hash_for_curve(atom)

@spec hash_for_curve(curve()) :: :sha256 | :sha384

Returns the hash a curve signs with: SHA-256 on P-256, SHA-384 on P-384.

Examples

iex> AwsEncryptionSdk.Crypto.ECDSA.hash_for_curve(:secp256r1)
:sha256

iex> AwsEncryptionSdk.Crypto.ECDSA.hash_for_curve(:secp384r1)
:sha384

normalize_public_key(uncompressed_key, curve)

@spec normalize_public_key(binary(), curve()) :: binary()

Normalizes a public key to uncompressed format.

Handles both compressed (0x02/0x03 prefix, 49 bytes for P-384 or 33 bytes for P-256) and uncompressed (0x04 prefix, 97 bytes for P-384 or 65 bytes for P-256) formats.

The curve is auto-detected from the key size when possible:

  • 33 bytes compressed or 65 bytes uncompressed → secp256r1
  • 49 bytes compressed or 97 bytes uncompressed → secp384r1

sign(message, private_key, curve)

@spec sign(binary(), binary(), curve()) :: binary()

Generates an ECDSA signature over the given message, with the curve's hash (SHA-256 on P-256, SHA-384 on P-384).

Parameters

  • message - Binary data to sign
  • private_key - Raw private key bytes (32 bytes for P-256, 48 for P-384)
  • curve - Elliptic curve to use (:secp256r1 or :secp384r1)

Returns

  • DER-encoded ECDSA signature

Examples

iex> {private_key, _public_key} = AwsEncryptionSdk.Crypto.ECDSA.generate_key_pair(:secp384r1)
iex> message = "test message"
iex> signature = AwsEncryptionSdk.Crypto.ECDSA.sign(message, private_key, :secp384r1)
iex> is_binary(signature)
true

sign_digest(digest, private_key)

@spec sign_digest(binary(), binary()) :: binary()

Signs a pre-computed digest using ECDSA.

This is useful for streaming where the hash is accumulated incrementally. The digest's size picks the curve: a 32-byte SHA-256 digest signs on P-256, a 48-byte SHA-384 digest on P-384.

Parameters

  • digest - SHA-256 digest (32 bytes) or SHA-384 digest (48 bytes)
  • private_key - Raw private key bytes

Returns

  • DER-encoded ECDSA signature

Examples

iex> {private_key, _public_key} = AwsEncryptionSdk.Crypto.ECDSA.generate_key_pair(:secp384r1)
iex> digest = :crypto.hash(:sha384, "test message")
iex> signature = AwsEncryptionSdk.Crypto.ECDSA.sign_digest(digest, private_key)
iex> is_binary(signature)
true

verify(message, signature, public_key, curve)

@spec verify(binary(), binary(), binary(), curve()) :: boolean()

Verifies an ECDSA signature over the given message, with the curve's hash (SHA-256 on P-256, SHA-384 on P-384).

Parameters

  • message - Binary data that was signed
  • signature - DER-encoded ECDSA signature
  • public_key - Raw public key bytes, compressed or uncompressed
  • curve - Elliptic curve to use (:secp256r1 or :secp384r1)

Returns

  • true if signature is valid
  • false if signature is invalid, including for a public key that is not a point on curve

Examples

iex> {private_key, public_key} = AwsEncryptionSdk.Crypto.ECDSA.generate_key_pair(:secp384r1)
iex> message = "test message"
iex> signature = AwsEncryptionSdk.Crypto.ECDSA.sign(message, private_key, :secp384r1)
iex> AwsEncryptionSdk.Crypto.ECDSA.verify(message, signature, public_key, :secp384r1)
true

verify_digest(digest, signature, public_key)

@spec verify_digest(binary(), binary(), binary()) :: boolean()

Verifies an ECDSA signature against a pre-computed digest.

The digest's size picks the curve, as for sign_digest/2.

Parameters

  • digest - SHA-256 digest (32 bytes) or SHA-384 digest (48 bytes)
  • signature - DER-encoded ECDSA signature
  • public_key - Raw public key bytes

Returns

  • true if valid, false otherwise

Examples

iex> {private_key, public_key} = AwsEncryptionSdk.Crypto.ECDSA.generate_key_pair(:secp384r1)
iex> digest = :crypto.hash(:sha384, "test message")
iex> signature = AwsEncryptionSdk.Crypto.ECDSA.sign_digest(digest, private_key)
iex> AwsEncryptionSdk.Crypto.ECDSA.verify_digest(digest, signature, public_key)
true