pw_crypto#

Stable

A set of safe (read: easy to use, hard to misuse) crypto APIs.

The following crypto services are provided by this module.

  1. Hashing a message with SHA256.

  2. Verifying a digital signature signed with ECDSA over the NIST P256 curve.

  3. Many more to come …

SHA256#

  1. Obtaining a oneshot digest.

#include "pw_crypto/sha256.h"

std::byte digest[32];
if (!pw::crypto::sha256::Hash(message, digest).ok()) {
  // Handle errors.
}

// The content can also come from a pw::stream::Reader.
if (!pw::crypto::sha256::Hash(reader, digest).ok()) {
  // Handle errors.
}
  1. Hashing a long, potentially non-contiguous message.

#include "pw_crypto/sha256.h"

std::byte digest[32];

if (!pw::crypto::sha256::Sha256()
    .Update(chunk1).Update(chunk2).Update(chunk...)
    .Final().ok()) {
  // Handle errors.
}

ECDSA#

  1. Verifying a digital signature signed with ECDSA over the NIST P256 curve.

#include "pw_crypto/sha256.h"

std::byte digest[32];
if (!pw::crypto::sha256::Hash(message, digest).ok()) {
  // handle errors.
}

if (!pw::crypto::ecdsa::VerifyP256Signature(public_key, digest,
                                            signature).ok()) {
  // handle errors.
}
  1. Verifying a digital signature signed with ECDSA over the NIST P256 curve, with a long and/or non-contiguous message.

#include "pw_crypto/sha256.h"

std::byte digest[32];

if (!pw::crypto::sha256::Sha256()
    .Update(chunk1).Update(chunk2).Update(chunkN)
    .Final(digest).ok()) {
    // Handle errors.
}

if (!pw::crypto::ecdsa::VerifyP256Signature(public_key, digest,
                                            signature).ok()) {
    // Handle errors.
}

AES#

  1. Computing the AES-CMAC of a potentially long and/or non-contiguous message. This is similar to a hash or digest except that the operation takes a secret key as an input, so the MAC can be used to verify integrity and authentication.

#include "pw_crypto/aes.h"

std::byte mac[16];

if (!pw::crypto::aes_cmac::Cmac(key).Update(chunk1).Update(chunk2)
      .Update(chunk...).Final().ok()) {
  // Handle errors.
}
  1. Encrypting a single AES 128-bit block.

Warning

This is a low-level operation. Users should know exactly what they are doing and must ensure that this operation does not violate any safety bounds that more refined operations usually ensure.

#include "pw_crypto/aes.h"

std::byte encrypted[16];

if (!pw::crypto::unsafe::aes::EncryptBlock(key, message, encrypted).ok()) {
    // Handle errors.
}

ECDH#

  1. Generating a keypair and computing a shared symmetric key.

Warning

Ensure that the backend is initialized and configured correctly with a cryptographically secure pseudo-random number generator (CSPRNG). The details for doing this are specific to each backend.

#include "pw_crypto/ecdh.h"

// Import the public key from the other party.
PW_TRY_ASSIGN(
   auto public_key,
   pw::crypto::ecdh::P256PublicKey::Import(other_x, other_y, endian));
PW_TRY_ASSIGN(auto keypair,
              pw::crypto::ecdh::P256Keypair::Generate());

std::byte shared_key[32];
if (!keypair.ComputeDiffieHellman(public_key, shared_key)) {
   // handle errors.
}
  1. Import a pre-existing keypair (for testing purposes) and computing a shared symmetric key.

#include "pw_crypto/ecdh.h"

// Import the public key from the other party.
PW_TRY_ASSIGN(
   auto public_key,
   pw::crypto::ecdh::P256PublicKey::Import(other_x, other_y, endian));
PW_TRY_ASSIGN(auto keypair,
   pw::crypto::ecdh::P256Keypair::ImportForTesting(
      private_key, x, y, endian));

std::byte shared_key[32];
if (!keypair.ComputeDiffieHellman(public_key, shared_key)) {
   // handle errors.
}

Configuration#

The crypto services offered by pw_crypto can be backed by different backend crypto libraries.

Mbed TLS#

The Mbed TLS project is a mature and full-featured crypto library that implements cryptographic primitives, X.509 certificate manipulation and the SSL/TLS and DTLS protocols.

The project also has good support for interfacing to cryptographic accelerators.

The small code footprint makes the project suitable and popular for embedded systems.

To select the Mbed TLS backend, the MbedTLS library needs to be installed and configured. If using GN, do,

# Install and configure MbedTLS
pw package install mbedtls
gn gen out --args='
    dir_pw_third_party_mbedtls=getenv("PW_PACKAGE_ROOT")+"/mbedtls"
    pw_crypto_SHA256_BACKEND="//pw_crypto:sha256_mbedtls_v3"
    pw_crypto_ECDSA_BACKEND="//pw_crypto:ecdsa_mbedtls_v3"
    pw_crypto_AES_BACKEND="//pw_crypto:aes_mbedtls_v3"
'

ninja -C out

If using Bazel, add a bazel_dep on Mbed TLS to your MODULE.bazel file and select appropriate backends by adding them to your project’s platform:

platform(
  name = "my_platform",
  flags = [
     "@pigweed//pw_crypto:sha256_backend=@pigweed//pw_crypto:sha256_mbedtls_backend",
     "@pigweed//pw_crypto:ecdsa_backend=@pigweed//pw_crypto:ecdsa_mbedtls_backend",
     "@pigweed//pw_crypto:aes_backend=@pigweed//pw_crypto:aes_mbedtls_backend",
     # ... other flags
   ],
)

For optimal code size and/or performance, the Mbed TLS library can be configured per product. Mbed TLS configuration is achieved by turning on and off MBEDTLS_* options in a config.h file. See //third_party/mbedtls for how this is done.

pw::crypto::sha256 does not need any special configuration as it uses the mbedtls_sha256_* APIs directly. However you can optionally turn on MBEDTLS_SHA256_SMALLER to further reduce the code size to from 3KiB to ~1.8KiB at a ~30% slowdown cost (Cortex-M4).

#define MBEDTLS_SHA256_SMALLER

pw::crypto::ecdsa requires the following minimum configurations which yields a code size of ~12KiB.

#define MBEDTLS_BIGNUM_C
#define MBEDTLS_ECP_C
#define MBEDTLS_ECDSA_C
// The ASN1 options are needed only because mbedtls considers and verifies
// them (in check_config.h) as dependencies of MBEDTLS_ECDSA_C.
#define MBEDTLS_ASN1_WRITE_C
#define MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_ECP_NO_INTERNAL_RNG
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED

BoringSSL#

The BoringSSL project (source, GitHub mirror) is a fork of OpenSSL maintained by Google. It is not especially designed to be embedded-friendly, but it is used as the SSL library in Chrome, Android, and other apps. It is likely better to use another backend such as Mbed-TLS for embedded targets unless your project needs BoringSSL specifically.

To use the BoringSSL backend with a GN project, it needs to be installed and configured. To do that:

# Install and configure BoringSSL
pw package install boringssl
gn gen out --args='
    dir_pw_third_party_boringssl=getenv("PW_PACKAGE_ROOT")+"/boringssl"
    pw_crypto_AES_BACKEND="//pw_crypto:aes_boringssl"
    pw_crypto_ECDH_BACKEND="//pw_crypto:ecdh_boringssl"
'

ninja -C out

If using Bazel, add the BoringSSL repository to your MODULE.bazel and select appropriate backends by adding them to your project’s platform:

platform(
  name = "my_platform",
  constraint_values = [
    "@pigweed//pw_aes:aes_boringssl_backend",
    # ... other constraint_values
  ],
)

Size Reports#

Below are size reports for each crypto service. These vary across configurations.

Label

Segment

Delta

SHA256

FLASH

+16

[section .rodata]

-4

__bi_84

+4

vClearInterruptMaskFromISR

+20

main

NEW

+2,378

mbedtls_internal_sha256_process

NEW

+282

mbedtls_sha256_finish_ret

NEW

+256

K

NEW

+144

mbedtls_sha256_starts_ret

NEW

+120

mbedtls_sha256_update_ret

NEW

+104

pw::crypto::sha256::Sha256::Final()

NEW

+64

pw::crypto::sha256::Sha256::Update()

NEW

+44

pw::crypto::sha256::Sha256::Sha256()

NEW

+40

pw::crypto::sha256::Hash()

NEW

+24

mbedtls_platform_zeroize

NEW

+24

pw::crypto::sha256::backend::DoInit()

NEW

+20

pw::crypto::sha256::backend::DoUpdate()

NEW

+16

pw::crypto::sha256::backend::DoFinal()

NEW

+12

mbedtls_sha256_init

NEW

+4

memset_func

+3,568

ECDSA P256 Verify

FLASH

+164

[section .rodata]

+2

__llvm_libc_21_0_0_git::FreeList::remove()

-4

__bi_84

+32

main

+2

fit::internal::null_target_move()

NEW

+1,924

mbedtls_ecp_mul_restartable

NEW

+1,176

mbedtls_ecp_group_load

NEW

+1,048

mbedtls_internal_aes_encrypt

NEW

+1,028

mbedtls_internal_aes_decrypt

NEW

+984

mbedtls_aes_setkey_enc

NEW

+942

ecp_mod_p384

NEW

+824

mpi_mul_hlp

NEW

+816

mbedtls_mpi_div_mpi

NEW

+802

ecp_mod_p256

NEW

+628

mbedtls_mpi_inv_mod

NEW

+608

ecp_double_jac

NEW

+580

ecp_add_mixed

NEW

+468

mbedtls_ecp_check_pubkey

NEW

+442

__llvm_libc_21_0_0_git::Block::allocate()

NEW

+424

ecp_mod_p224

NEW

+412

ecp_normalize_jac_many

NEW

+406

__llvm_libc_21_0_0_git::FreeTrie::find_best_fit()

NEW

+374

__llvm_libc_21_0_0_git::FreeListHeap::allocate_impl()

NEW

+356

block_cipher_df

NEW

+328

ecdsa_verify_restartable

NEW

+252

mbedtls_mpi_mul_mpi

NEW

+250

mbedtls_mpi_gcd

NEW

+240

mbedtls_mpi_random

NEW

+222

mbedtls_ctr_drbg_random_with_add

NEW

+220

mbedtls_ecp_point_read_binary

NEW

+212

ecp_mod_koblitz

NEW

+204

ecp_mod_p448

NEW

+202

mbedtls_mpi_sub_abs

NEW

+192

pw::crypto::ecdsa::VerifyP256Signature()

NEW

+184

mbedtls_ctr_drbg_reseed_internal

NEW

+180

mbedtls_mpi_add_abs

NEW

+176

mbedtls_mpi_mul_mod

NEW

+172

ecp_mod_p192

NEW

+168

mbedtls_ecp_mul_shortcuts

NEW

+160

ecp_randomize_jac

NEW

+152

mbedtls_mpi_cmp_mpi

NEW

+152

mbedtls_mpi_shift_l

NEW

+148

ecp_group_load

NEW

+148

ecp_normalize_jac

NEW

+148

mbedtls_mpi_lt_mpi_ct

NEW

+144

ecp_mod_p255

NEW

+138

ctr_drbg_update_internal

NEW

+132

mbedtls_mpi_cmp_abs

NEW

+132

mbedtls_mpi_shift_r

NEW

+124

mbedtls_ecp_check_privkey

NEW

+122

mbedtls_mpi_write_binary

NEW

+120

mbedtls_ecp_muladd_restartable

NEW

+120

mbedtls_mpi_shrink

NEW

+114

mbedtls_mpi_safe_cond_swap

NEW

+106

mbedtls_mpi_copy

NEW

+104

ecp_mod_p521

NEW

+100

mbedtls_ctr_drbg_seed

NEW

+98

mbedtls_mpi_mod_mpi

NEW

+96

ecp_randomize_mxz

NEW

+96

mbedtls_ecp_group_free

NEW

+94

add_sub_mpi

NEW

+92

mbedtls_mpi_grow

NEW

+88

derive_mpi

NEW

+88

ecp_select_comb

NEW

+88

mbedtls_mpi_mul_int

NEW

+88

mpi_fill_random_internal

NEW

+78

mbedtls_mpi_safe_cond_assign

NEW

+76

ecp_safe_invert_jac

NEW

+74

mbedtls_mpi_set_bit

NEW

+72

ecp_drbg_seed

NEW

+70

mbedtls_mpi_bitlen

NEW

+68

calloc

NEW

+68

mbedtls_mpi_read_binary_le

NEW

+68

secp521r1_b

NEW

+68

secp521r1_gx

NEW

+68

secp521r1_gy

NEW

+68

secp521r1_n

NEW

+68

secp521r1_p

NEW

+66

mbedtls_mpi_sub_mod

NEW

+64

brainpoolP512r1_a

NEW

+64

brainpoolP512r1_b

NEW

+64

brainpoolP512r1_gx

NEW

+64

brainpoolP512r1_gy

NEW

+64

brainpoolP512r1_n

NEW

+64

brainpoolP512r1_p

NEW

+62

mbedtls_mpi_read_binary

NEW

+58

add64

NEW

+58

mbedtls_ecp_fix_negative

NEW

+58

mbedtls_mpi_lsb

NEW

+56

mbedtls_ecp_group_init

NEW

+56

mbedtls_mpi_lset

NEW

+56

mbedtls_mpi_resize_clear

NEW

+54

mbedtls_mpi_add_mod

NEW

+52

mbedtls_mpi_shift_l_mod

NEW

+48

brainpoolP384r1_a

NEW

+48

brainpoolP384r1_b

NEW

+48

brainpoolP384r1_gx

NEW

+48

brainpoolP384r1_gy

NEW

+48

brainpoolP384r1_n

NEW

+48

brainpoolP384r1_p

NEW

+48

secp384r1_b

NEW

+48

secp384r1_gx

NEW

+48

secp384r1_gy

NEW

+48

secp384r1_n

NEW

+48

secp384r1_p

NEW

+46

mbedtls_ecp_copy

NEW

+46

mpi_sub_hlp

NEW

+42

mbedtls_ecp_set_zero

NEW

+40

mbedtls_ctr_drbg_free

NEW

+38

mbedtls_mpi_add_int

NEW

+38

mbedtls_mpi_cmp_int

NEW

+38

mbedtls_mpi_free

NEW

+38

mbedtls_mpi_sub_int

NEW

+36

carry64

NEW

+36

fit::internal::target<>::invoke()

NEW

+32

brainpoolP256r1_a

NEW

+32

brainpoolP256r1_b

NEW

+32

brainpoolP256r1_gx

NEW

+32

brainpoolP256r1_gy

NEW

+32

brainpoolP256r1_n

NEW

+32

brainpoolP256r1_p

NEW

+32

mbedtls_ctr_drbg_init

NEW

+32

mbedtls_ecp_point_free

NEW

+32

mbedtls_mpi_get_bit

NEW

+32

secp224k1_n

NEW

+32

secp224r1_p

NEW

+32

secp256k1_gx

NEW

+32

secp256k1_gy

NEW

+32

secp256k1_n

NEW

+32

secp256k1_p

NEW

+32

secp256r1_b

NEW

+32

secp256r1_gx

NEW

+32

secp256r1_gy

NEW

+32

secp256r1_n

NEW

+32

secp256r1_p

NEW

+32

x25519_bad_point_1

NEW

+32

x25519_bad_point_2

NEW

+30

mpi_bigendian_to_host

NEW

+28

curve448_part_of_n

NEW

+28

mbedtls_aes_crypt_ecb

NEW

+28

mbedtls_ct_mpi_uint_cond_assign

NEW

+28

mbedtls_ecp_point_init

NEW

+28

secp224k1_gx

NEW

+28

secp224k1_gy

NEW

+28

secp224k1_p

NEW

+28

secp224r1_b

NEW

+28

secp224r1_gx

NEW

+28

secp224r1_gy

NEW

+28

secp224r1_n

NEW

+24

__aeabi_uidivmod

NEW

+24

mbedtls_platform_zeroize

NEW

+24

secp192k1_gx

NEW

+24

secp192k1_gy

NEW

+24

secp192k1_n

NEW

+24

secp192k1_p

NEW

+24

secp192r1_b

NEW

+24

secp192r1_gx

NEW

+24

secp192r1_gy

NEW

+24

secp192r1_n

NEW

+24

secp192r1_p

NEW

+20

fit::internal::inline_trivial_target_move<>()

NEW

+20

fit::internal::target<>::ops

NEW

+18

mbedtls_aes_free

NEW

+16

__aeabi_idivmod

NEW

+16

curve25519_part_of_n

NEW

+16

ecp_ctr_drbg_null_entropy

NEW

+16

ecp_mod_p192k1

NEW

+16

ecp_mod_p224k1

NEW

+16

ecp_mod_p256k1

NEW

+16

mbedtls_ecdsa_verify

NEW

+16

mbedtls_ecp_is_zero

NEW

+12

ecp_x25519_bad_point_1

NEW

+12

ecp_x25519_bad_point_2

NEW

+12

mbedtls_aes_init

NEW

+12

mbedtls_ctr_drbg_random

NEW

+12

mbedtls_mpi_init

NEW

+12

mbedtls_mpi_size

NEW

+12

mbedtls_mpi_sub_mpi

NEW

+10

mbedtls_ctr_drbg_reseed

NEW

+10

mbedtls_mpi_add_mpi

NEW

+10

mbedtls_mpi_zeroize

NEW

+8

ecp_drbg_random

NEW

+8

ecp_mod_p192k1.Rp

NEW

+8

ecp_mod_p224k1.Rp

NEW

+8

ecp_mod_p256k1.Rp

NEW

+4

ecp_mpi_set1.one

NEW

+4

memset_func

NEW

+4

secp192k1_a

NEW

+4

secp192k1_b

NEW

+4

secp224k1_a

NEW

+4

secp224k1_b

NEW

+4

secp256k1_a

NEW

+4

secp256k1_b

NEW

+2

fit::internal::inline_target_get()

+25,236

RAM

NEW

+1,024

FT0

NEW

+1,024

FT1

NEW

+1,024

FT2

NEW

+1,024

FT3

NEW

+1,024

RT0

NEW

+1,024

RT1

NEW

+1,024

RT2

NEW

+1,024

RT3

NEW

+256

FSb

NEW

+256

RSb

NEW

+56

ecp_supported_grp_id

NEW

+40

round_constants

NEW

+4

add_count

NEW

+4

aes_init_done

NEW

+4

dbl_count

NEW

+4

mbedtls_ecp_grp_id_list.init_done

NEW

+4

mul_count

+8,820

API reference#

Status pw::crypto::ecdsa::VerifyP256Signature(ConstByteSpan public_key, ConstByteSpan digest, ConstByteSpan signature)#

Verifies the signature of digest using public_key.

Example:

#include "pw_crypto/sha256.h"

// Verify a digital signature signed with ECDSA over the NIST P256 curve.
std::byte digest[32];
if (!pw::crypto::sha256::Hash(message, digest).ok()) {
    // handle errors.
}

if (!pw::crypto::ecdsa::VerifyP256Signature(public_key, digest,
                                            signature).ok()) {
    // handle errors.
}
Parameters:
  • public_key[in] A byte string in SEC 1 uncompressed form (0x04||X||Y), which is exactly 65 bytes. Compressed forms (02/03||X) may not be supported by some backends, e.g. Mbed TLS.

  • digest[in] A raw byte string, truncated to 32 bytes.

  • signature[in] A raw byte string (r||s) of exactly 64 bytes.

Returns:

OK for a successful verification, or an error Status otherwise.

inline Status pw::crypto::sha256::Hash(ConstByteSpan message, ByteSpan out_digest)#

Calculates the SHA256 digest of message and stores the result in out_digest. out_digest must be at least kDigestSizeBytes long.

One-shot digest example:

#include "pw_crypto/sha256.h"

std::byte digest[32];
if (!pw::crypto::sha256::Hash(message, digest).ok()) {
    // Handle errors.
}

// The content can also come from a pw::stream::Reader.
if (!pw::crypto::sha256::Hash(reader, digest).ok()) {
    // Handle errors.
}

Long, potentially non-contiguous message example:

#include "pw_crypto/sha256.h"

std::byte digest[32];

if (!pw::crypto::sha256::Sha256()
    .Update(chunk1).Update(chunk2).Update(chunk...)
    .Final().ok()) {
    // Handle errors.
}
inline Status pw::crypto::sha256::Hash(stream::Reader &reader, ByteSpan out_digest)#
constexpr uint32_t pw::crypto::sha256::kDigestSizeBytes = 32#

The size of a SHA256 digest in bytes.

inline Status pw::crypto::sha256::Sha256::Final(ByteSpan out_digest)#

Finishes the hashing session and outputs the final digest in the first kDigestSizeBytes of out_digest. out_digest must be at least kDigestSizeBytes long.

Final() locks down the Sha256 instance from any additional use.

Any error, including those occurring inside the constructor or Update() will be reflected in the return value of Final().

inline Sha256 &pw::crypto::sha256::Sha256::Update(ConstByteSpan data)#

Feeds data to the running hasher. The feeding can involve zero or more Update() calls and the order matters.

enum class pw::crypto::sha256::Sha256State#

A state machine of a hashing session.

Values:

enumerator kReady#

Initialized and accepting input (via Update()).

enumerator kFinalized#

Finalized by Final(). Any additional requests to Update() or Final() will trigger a transition to kError.

enumerator kError#

In an unrecoverable error state.