#aead #encryption #aes #zero-allocation #data #mode #ccm

no-std aes-ccm

A pure-Rust, #![no_std], zero-allocation AES-CCM implementation ported from TinyCrypt using RustCrypto's AES

7 releases (4 breaking)

0.5.0 Jun 13, 2020
0.4.1 Mar 17, 2020
0.4.0 Dec 1, 2019
0.3.0 Sep 9, 2019
0.1.0 Aug 19, 2019

#2696 in Cryptography

Download history 51/week @ 2024-07-01 135/week @ 2024-07-08 43/week @ 2024-07-15 33/week @ 2024-07-22 47/week @ 2024-07-29 33/week @ 2024-08-05 9/week @ 2024-08-12 22/week @ 2024-08-19 13/week @ 2024-08-26 22/week @ 2024-09-02 21/week @ 2024-09-09 24/week @ 2024-09-16 48/week @ 2024-09-23 6/week @ 2024-09-30 22/week @ 2024-10-07 24/week @ 2024-10-14

100 downloads per month

MIT/Apache

47KB
789 lines

aes-ccm

Latest version Documentation License

A pure-Rust, #![no_std], zero-allocation AES-CCM implementation ported from TinyCrypt using RustCrypto's AES (with support for optionally swapping in hardware-backed implementations).

It implements the aead::AeadInPlace trait, so it can be used effortlessly together with other implementations.

Overview

CCM (for "Counter with CBC-MAC") mode is a NIST approved mode of operation defined in SP 800-38C.

This implementation accepts:

  1. Both non-empty payload and associated data (it encrypts and authenticates the payload and also authenticates the associated data).
  2. Non-empty payload and empty associated data (it encrypts and authenticates the payload).
  3. Non-empty associated data and empty payload (it degenerates to an authentication mode on the associated data).

The implementation accepts payloads of any length between 0 and 2^16 bytes and associated data of any length between 0 and (2^16 - 2^8) bytes.

Usage

use aes_ccm::{
    aead::{consts::U8, Aead, NewAead, Payload},
    Aes128Ccm,
};

let key = [
    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
    0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
];

// `U8` represents the tag size as a `typenum` unsigned (8-bytes here)
let ccm = Aes128Ccm::<U8>::new(&key.into());

let nonce = [
    0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0xA0, 0xA1, 0xA2, 0xA3,
    0xA4, 0xA5,
];
let msg = [
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
    0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
    0x1E,
];
let associated_data = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];

let ciphertext = ccm
    .encrypt(
        &nonce.into(),
        Payload {
            aad: &associated_data,
            msg: &msg,
        },
    )
    .unwrap();

let plaintext = ccm
    .decrypt(
        &nonce.into(),
        Payload {
            aad: &associated_data,
            msg: &ciphertext,
        },
    )
    .unwrap();

assert_eq!(&msg[..], plaintext.as_slice());

In-place Usage (eliminates alloc requirement)

This crate has an optional alloc feature which can be disabled in e.g. microcontroller environments that don't have a heap.

The AeadInPlace::encrypt_in_place and AeadInPlace::decrypt_in_place methods accept any type that impls the aead::Buffer trait which contains the plaintext for encryption or ciphertext for decryption.

Note that if you enable the heapless feature of this crate, you will receive an impl of aead::Buffer for heapless::Vec (re-exported from the aead crate as aead::heapless::Vec), which can then be passed as the buffer parameter to the in-place encrypt and decrypt methods:

use aes_ccm::{
    aead::{
        consts::{U128, U8},
        heapless::Vec,
        AeadInPlace, NewAead,
    },
    Aes128Ccm,
};

let key = [
    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
    0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
];

// `U8` represents the tag size as a `typenum` unsigned (8-bytes here)
let ccm = Aes128Ccm::<U8>::new(&key.into());

let nonce = [
    0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0xA0, 0xA1, 0xA2, 0xA3,
    0xA4, 0xA5,
];
let associated_data = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
let plaintext = [
    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
    0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
    0x1E,
];

let mut buffer: Vec<u8, U128> = Vec::new();
buffer.extend_from_slice(&plaintext).unwrap();

// Encrypt `buffer` in-place, replacing the plaintext contents with
// ciphertext
ccm.encrypt_in_place(&nonce.into(), &associated_data, &mut buffer)
    .unwrap();
// `buffer` now contains the message ciphertext
assert_ne!(&buffer, &plaintext);

// Decrypt `buffer` in-place, replacing its ciphertext contents with the
// original plaintext
ccm.decrypt_in_place(&nonce.into(), &associated_data, &mut buffer)
    .unwrap();
assert_eq!(&buffer, &plaintext);

Security

I'm not a cryptographer and this hasn't been audited in any way. It is however a careful port of TinyCrypt, so if it's sound, then this should be too.

The MAC length parameter is an important parameter to estimate the security against collision attacks (that aim at finding different messages that produce the same authentication tag). The implementation accepts any even integer between 4 and 16, as suggested in SP 800-38C.

RFC 3610, which also specifies CCM, presents a few relevant security suggestions, such as:

  • It is recommended that most applications use a MAC length greater than 8.
  • The usage of the same nonce for two different messages which are encrypted with the same key destroys the security of CCM mode.

License

Licensed under either of

at your option.

This is a port of TinyCrypt's CCM mode, its license file is in LICENSE-3RD-PARTY.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~595KB
~13K SLoC