silk.crypto.aes.

Encrypt data with the AES block cipher (FIPS 197) in ECB, CBC, CTR, CFB, or OFB mode (NIST SP 800-38A).

When to use#

Use this module when a protocol or file format names AES-CBC, AES-CTR, AES-ECB, AES-CFB, or AES-OFB, or when it needs the raw AES block function. These modes do not detect changes to the ciphertext. To encrypt new data, prefer silk.crypto.aes_gcm or silk.crypto.chacha20_poly1305, which also authenticate it.

Details#

Aes holds an expanded 16-byte (AES-128), 24-byte (AES-192), or 32-byte (AES-256) key and encrypts or decrypts one 16-byte block. AesCbc and AesEcb encrypt a message that arrives in parts: call update for each part and finish at the end. With padding on, they add and remove PKCS #7 padding, as OpenSSL does. AesCtr XORs a key stream into the data, so the same call encrypts and decrypts. The counter is the whole 16-byte block, incremented as one big-endian number. AesCfb, AesCfb8, and AesCfb1 are cipher feedback with 128, 8, and 1-bit segments, and AesOfb is output feedback. Nothing allocates.

The other AES modes have their own modules: silk.crypto.aes_gcm, silk.crypto.aes_ccm, and silk.crypto.aes_ocb authenticate, silk.crypto.aes_xts encrypts storage sectors, and silk.crypto.aes_wrap wraps keys.

When the build targets x86-64 or AArch64 with the aes CPU feature, each round is one AES instruction. A default x86-64 build checks at run time for AES-NI and SSSE3 and uses them when the machine has them, with CBC decryption, ECB and CTR running eight blocks at a time. Otherwise the rounds use portable code that computes the S-box instead of reading a table, so the time does not depend on the key or the data on any target.

Errors#

The functions return an AesError. Every check comes before the first write, and on an error the output buffers do not change.

Gotchas#

CBC and CTR need a new IV for each message under the same key. With CTR, the same key and IV used twice give away the XOR of the two plaintexts. ECB encrypts equal blocks to equal blocks, so it shows patterns in the data. A wrong padding error from CBC decryption can act as an oracle that lets an attacker decrypt the message, so authenticate the ciphertext before you decrypt it. The values do not erase their copies of the key.

Examples#

Encrypt and decrypt with AES-128-CBC#

silk
import silk.crypto.aes { AesCbc, AesError }
import silk.core.result { Result }

pub fn main() -> i32 {
  let key: [u8; 16] = [0; 16]
  let iv: [u8; 16] = [0; 16]
  let mut encryptor = match AesCbc.encryptor(&key, &iv, true) {
    Result<AesCbc, AesError>.Success { value } => move value
    Result<AesCbc, AesError>.Failure { error: _ } => {
      return 1
    }
  }
  // Five bytes of plaintext become one padded block of ciphertext.
  let mut ciphertext: [u8; 16] = [0; 16]
  let mut written: usize = 0
  if let Result.Success { value } = encryptor.update(b"hello", &mut ciphertext) {
    written = value
  }
  if let Result.Success { value } = encryptor.finish(&mut ciphertext) {
    written = written + value
  }
  if written != 16 {
    return 1
  }
  let mut decryptor = match AesCbc.decryptor(&key, &iv, true) {
    Result<AesCbc, AesError>.Success { value } => move value
    Result<AesCbc, AesError>.Failure { error: _ } => {
      return 1
    }
  }
  let mut plaintext: [u8; 16] = [0; 16]
  if let Result.Success { value } = decryptor.update(&ciphertext, &mut plaintext) {
    if value != 0 {
      return 1
    }
  }
  return match decryptor.finish(&mut plaintext) {
    Result<usize, AesError>.Success { value } => {
      if value != 5 || plaintext[0] != 104 {
        return 1
      }
      return 0
    }
    Result<usize, AesError>.Failure { error: _ } => 1
  }
}

Encrypt with AES-256-CTR#

silk
import silk.crypto.aes { AesCtr, AesError }
import silk.core.result { Result }

pub fn main() -> i32 {
  let key: [u8; 32] = [1; 32]
  let iv: [u8; 16] = [0; 16]
  let mut stream = match AesCtr.make(&key, &iv) {
    Result<AesCtr, AesError>.Success { value } => move value
    Result<AesCtr, AesError>.Failure { error: _ } => {
      return 1
    }
  }
  let mut ciphertext: [u8; 5] = [0; 5]
  if let Result.Failure { error: _ } = stream.apply(b"hello", &mut ciphertext) {
    return 1
  }
  // A new stream with the same key and IV decrypts.
  let mut again = match AesCtr.make(&key, &iv) {
    Result<AesCtr, AesError>.Success { value } => move value
    Result<AesCtr, AesError>.Failure { error: _ } => {
      return 1
    }
  }
  let mut plaintext: [u8; 5] = [0; 5]
  if let Result.Failure { error: _ } = again.apply(&ciphertext, &mut plaintext) {
    return 1
  }
  if plaintext[0] != 104 || plaintext[4] != 111 {
    return 1
  }
  return 0
}

Import as Aes with import silk.crypto.aes { Aes }.

Public declarations: 11.

AesError#

silk
pub union AesError

The reason that an AES function rejected a call. The output buffers do not change.

InvalidKeyLength#

silk
InvalidKeyLength

The key is not 16, 24, or 32 bytes.

InvalidIvLength#

silk
InvalidIvLength

The IV is not 16 bytes.

OutputTooSmall#

silk
OutputTooSmall

The output buffer is shorter than the bytes the call writes.

InvalidInputLength#

silk
InvalidInputLength

Without padding, the message is not a whole number of 16-byte blocks. With padding, the ciphertext given to a decryptor is not a whole number of blocks.

InvalidPadding#

silk
InvalidPadding

The last decrypted block does not end in valid PKCS #7 padding. The key or IV is wrong, or the ciphertext was changed.

Implementation Debug for AesError#

silk
impl Debug for AesError

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> ()
    ! WriterError
    ? &mut Writer

Writes the name of the variant, such as InvalidKeyLength.

Aes#

silk
pub struct Aes

An expanded AES key (FIPS 197) that encrypts and decrypts single 16-byte blocks.

When to use#

Use it to build a block cipher mode that this module does not offer. For CBC, CTR, or ECB, use AesCbc, AesCtr, or AesEcb. For new designs, use silk.crypto.aes_gcm.

Details#

Aes.make takes a 16-byte (AES-128), 24-byte (AES-192), or 32-byte (AES-256) key and expands the encryption and decryption round keys once. The value is fixed-size and does not allocate. With the aes CPU feature on x86-64 or AArch64, or with AES-NI found at run time on x86-64, each round is one instruction. Otherwise the portable rounds compute the S-box instead of reading a table, so the time does not depend on the key or the data.

Gotchas#

Encrypting blocks one by one with the same key is ECB mode, which shows equal blocks. The value holds key material and is not erased when it is dropped.

Implementation Aes#

silk
impl Aes

Associated function make#

silk
pub fn make(key: &[u8]) -> Result<Aes, AesError>

Expands key into an AES key.

The key is 16 bytes (AES-128, 10 rounds), 24 bytes (AES-192, 12 rounds), or 32 bytes (AES-256, 14 rounds). The value copies what it needs and does not borrow key.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

Method encryptBlock#

silk
pub fn encryptBlock(self: &Self, block: &[u8; 16]) -> [u8; 16]

Returns the encryption of the 16-byte block block.

The time does not depend on the key or the data.

Method decryptBlock#

silk
pub fn decryptBlock(self: &Self, block: &[u8; 16]) -> [u8; 16]

Returns the decryption of the 16-byte block block.

The time does not depend on the key or the data.

encryptState#

silk
pub fn encryptState(key: &Aes, state: simd<u8, 16>) -> simd<u8, 16>

Encrypts the 16-byte state state with key.

Use it to build a block cipher mode on SIMD states without converting to and from arrays. The lanes hold the block bytes in order. The time does not depend on the key or the data.

decryptState#

silk
pub fn decryptState(key: &Aes, state: simd<u8, 16>) -> simd<u8, 16>

Decrypts the 16-byte state state with key.

Use it to build a block cipher mode on SIMD states without converting to and from arrays. The lanes hold the block bytes in order. The time does not depend on the key or the data.

AesCbc#

silk
pub struct AesCbc

A streaming AES-CBC encryptor or decryptor (NIST SP 800-38A), with or without PKCS #7 padding.

When to use#

Use it when a protocol or file format names AES-CBC, such as aes-256-cbc in OpenSSL or Node.js. CBC does not detect changes to the ciphertext. For new designs, use silk.crypto.aes_gcm.

Details#

Make one with AesCbc.encryptor or AesCbc.decryptor, call AesCbc.update for each part of the input, then AesCbc.finish. update writes whole blocks and keeps the rest, so the output of all calls together does not depend on how the input was split. With padding on, a decryptor keeps the last whole block until finish, which checks and removes the padding. The key is 16, 24, or 32 bytes and the IV is 16 bytes. Nothing allocates.

Gotchas#

Use a new random IV for each message under the same key. A wrong padding error can act as an oracle that decrypts the message for an attacker, so authenticate the ciphertext first. The value holds key material and is not erased when it is dropped.

Implementation AesCbc#

silk
impl AesCbc

Associated function encryptor#

silk
pub fn encryptor(key: &[u8], iv: &[u8], padding: bool) -> Result<AesCbc, AesError>

Returns an encryptor for key and iv. With padding, AesCbc.finish adds PKCS #7 padding, so any message length is valid.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

  • InvalidIvLength if iv is not 16 bytes.

Associated function decryptor#

silk
pub fn decryptor(key: &[u8], iv: &[u8], padding: bool) -> Result<AesCbc, AesError>

Returns a decryptor for key and iv. With padding, AesCbc.finish checks and removes PKCS #7 padding.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

  • InvalidIvLength if iv is not 16 bytes.

Method updateLength#

silk
pub fn updateLength(self: &Self, length: usize) -> usize

Returns the number of bytes that AesCbc.update writes for length more input bytes.

It is the number of whole blocks in the kept bytes plus the input. A decryptor with padding keeps back its last whole block.

Method update#

silk
pub fn update(self: &mut Self, input: &[u8], output: &mut [u8]) -> Result<usize, AesError>

Encrypts or decrypts the whole blocks of the kept bytes plus input into output, keeps the rest, and returns the number of bytes written.

The count is AesCbc.updateLength of input.length, a multiple of 16. The bytes of output after it do not change. input and output must not overlap.

Errors#

Method finish#

silk
pub fn finish(self: Self, output: &mut [u8]) -> Result<usize, AesError>

Ends the message, writes the last bytes to output, returns their number, and consumes the state.

An encryptor with padding writes one 16-byte block. A decryptor with padding writes 0 to 15 bytes, the last block without its padding. Without padding, it writes nothing.

Errors#

  • InvalidInputLength without padding if the message is not a whole number of blocks, or for a decryptor with padding if the ciphertext is not a whole number of blocks.

  • InvalidPadding for a decryptor with padding if the last block does not end in valid PKCS #7 padding.

  • OutputTooSmall if output is shorter than the bytes to write.

AesEcb#

silk
pub struct AesEcb

A streaming AES-ECB encryptor or decryptor (NIST SP 800-38A), with or without PKCS #7 padding.

When to use#

Use it only when a protocol or file format requires AES-ECB, such as aes-128-ecb in OpenSSL or Node.js. ECB encrypts equal blocks to equal blocks, so it shows patterns in the data. For new designs, use silk.crypto.aes_gcm.

Details#

Make one with AesEcb.encryptor or AesEcb.decryptor, call AesEcb.update for each part of the input, then AesEcb.finish. update writes whole blocks and keeps the rest. With padding on, a decryptor keeps the last whole block until finish, which checks and removes the padding. The key is 16, 24, or 32 bytes. There is no IV. Nothing allocates.

Gotchas#

The value holds key material and is not erased when it is dropped.

Implementation AesEcb#

silk
impl AesEcb

Associated function encryptor#

silk
pub fn encryptor(key: &[u8], padding: bool) -> Result<AesEcb, AesError>

Returns an encryptor for key. With padding, AesEcb.finish adds PKCS #7 padding.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

Associated function decryptor#

silk
pub fn decryptor(key: &[u8], padding: bool) -> Result<AesEcb, AesError>

Returns a decryptor for key. With padding, AesEcb.finish checks and removes PKCS #7 padding.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

Method updateLength#

silk
pub fn updateLength(self: &Self, length: usize) -> usize

Returns the number of bytes that AesEcb.update writes for length more input bytes.

It is the number of whole blocks in the kept bytes plus the input. A decryptor with padding keeps back its last whole block.

Method update#

silk
pub fn update(self: &mut Self, input: &[u8], output: &mut [u8]) -> Result<usize, AesError>

Encrypts or decrypts the whole blocks of the kept bytes plus input into output, keeps the rest, and returns the number of bytes written.

The count is AesEcb.updateLength of input.length, a multiple of 16. The bytes of output after it do not change. input and output must not overlap.

Errors#

Method finish#

silk
pub fn finish(self: Self, output: &mut [u8]) -> Result<usize, AesError>

Ends the message, writes the last bytes to output, returns their number, and consumes the state.

An encryptor with padding writes one 16-byte block. A decryptor with padding writes 0 to 15 bytes, the last block without its padding. Without padding, it writes nothing.

Errors#

  • InvalidInputLength without padding if the message is not a whole number of blocks, or for a decryptor with padding if the ciphertext is not a whole number of blocks.

  • InvalidPadding for a decryptor with padding if the last block does not end in valid PKCS #7 padding.

  • OutputTooSmall if output is shorter than the bytes to write.

AesCtr#

silk
pub struct AesCtr

A streaming AES-CTR key stream (NIST SP 800-38A) that encrypts and decrypts.

When to use#

Use it when a protocol or file format names AES-CTR, such as aes-128-ctr in OpenSSL or Node.js. CTR does not detect changes to the ciphertext. For new designs, use silk.crypto.aes_gcm.

Details#

The 16-byte IV is the first counter block. Each block of key stream is the encryption of the counter, which then grows by one as a 128-bit big-endian number and wraps to zero after all ones, as OpenSSL does. AesCtr.apply XORs the key stream into the data, so the same call encrypts and decrypts, and the result does not depend on how the data is split. Nothing allocates.

Gotchas#

The same key and IV must never encrypt two messages. If they do, the XOR of the two ciphertexts is the XOR of the two plaintexts. The value holds key material and is not erased when it is dropped.

Implementation AesCtr#

silk
impl AesCtr

Associated function make#

silk
pub fn make(key: &[u8], iv: &[u8]) -> Result<AesCtr, AesError>

Returns a key stream for key that starts at the counter block iv.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

  • InvalidIvLength if iv is not 16 bytes.

Method apply#

silk
pub fn apply(self: &mut Self, input: &[u8], output: &mut [u8]) -> Result<(), AesError>

XORs the next input.length bytes of key stream with input into output.

It writes input.length bytes. The bytes of output after them do not change. input and output must not overlap.

Errors#

  • OutputTooSmall if output is shorter than input. The state does not change.

AesCfb#

silk
pub struct AesCfb

AES in full-block cipher feedback mode, CFB-128 (NIST SP 800-38A section 6.3).

When to use#

Use it where a protocol names AES-CFB, such as Node's aes-256-cfb. It does not detect changes to the ciphertext. For new designs, use silk.crypto.aes_gcm.

Details#

The 16-byte IV is the first input block. Each key stream block is the encryption of the previous 16 ciphertext bytes, so encryption and decryption differ: make the value with AesCfb.encryptor or AesCfb.decryptor. AesCfb.apply takes the data in any split and gives the same result. Nothing allocates.

Gotchas#

The same key and IV must never encrypt two messages. The value holds key material and is not erased when it is dropped.

Implementation AesCfb#

silk
impl AesCfb

Associated function encryptor#

silk
pub fn encryptor(key: &[u8], iv: &[u8]) -> Result<AesCfb, AesError>

Returns a CFB-128 encryptor for key and iv.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

  • InvalidIvLength if iv is not 16 bytes.

Associated function decryptor#

silk
pub fn decryptor(key: &[u8], iv: &[u8]) -> Result<AesCfb, AesError>

Returns a CFB-128 decryptor for key and iv.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

  • InvalidIvLength if iv is not 16 bytes.

Method apply#

silk
pub fn apply(self: &mut Self, input: &[u8], output: &mut [u8]) -> Result<(), AesError>

Encrypts or decrypts input into the first input.length bytes of output.

The bytes of output after them do not change. input and output must not overlap.

Errors#

  • OutputTooSmall if output is shorter than input. The state does not change.

AesCfb8#

silk
pub struct AesCfb8

AES in 8-bit cipher feedback mode, CFB-8 (NIST SP 800-38A section 6.3).

When to use#

Use it where a protocol names AES-CFB8, such as Node's aes-128-cfb8. It does not detect changes to the ciphertext, and it runs one block encryption per byte.

Details#

The 16-byte IV is the first shift register. For each byte, the first byte of the encrypted register is the key stream byte, and the register shifts left by one byte and takes the ciphertext byte. Make the value with AesCfb8.encryptor or AesCfb8.decryptor. Nothing allocates.

Gotchas#

The same key and IV must never encrypt two messages. The value holds key material and is not erased when it is dropped.

Implementation AesCfb8#

silk
impl AesCfb8

Associated function encryptor#

silk
pub fn encryptor(key: &[u8], iv: &[u8]) -> Result<AesCfb8, AesError>

Returns a CFB-8 encryptor for key and iv.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

  • InvalidIvLength if iv is not 16 bytes.

Associated function decryptor#

silk
pub fn decryptor(key: &[u8], iv: &[u8]) -> Result<AesCfb8, AesError>

Returns a CFB-8 decryptor for key and iv.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

  • InvalidIvLength if iv is not 16 bytes.

Method apply#

silk
pub fn apply(self: &mut Self, input: &[u8], output: &mut [u8]) -> Result<(), AesError>

Encrypts or decrypts input into the first input.length bytes of output.

The bytes of output after them do not change. input and output must not overlap.

Errors#

  • OutputTooSmall if output is shorter than input. The state does not change.

AesCfb1#

silk
pub struct AesCfb1

AES in 1-bit cipher feedback mode, CFB-1 (NIST SP 800-38A section 6.3).

When to use#

Use it where a protocol names AES-CFB1, such as Node's aes-128-cfb1. It does not detect changes to the ciphertext, and it runs one block encryption per bit, so it is slow.

Details#

The 16-byte IV is the first shift register. Each byte is taken as eight bits, most significant first, as OpenSSL does for byte input. For each bit, the top bit of the encrypted register is the key stream bit, and the register shifts left by one bit and takes the ciphertext bit. Make the value with AesCfb1.encryptor or AesCfb1.decryptor. Nothing allocates.

Gotchas#

The same key and IV must never encrypt two messages. The value holds key material and is not erased when it is dropped.

Implementation AesCfb1#

silk
impl AesCfb1

Associated function encryptor#

silk
pub fn encryptor(key: &[u8], iv: &[u8]) -> Result<AesCfb1, AesError>

Returns a CFB-1 encryptor for key and iv.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

  • InvalidIvLength if iv is not 16 bytes.

Associated function decryptor#

silk
pub fn decryptor(key: &[u8], iv: &[u8]) -> Result<AesCfb1, AesError>

Returns a CFB-1 decryptor for key and iv.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

  • InvalidIvLength if iv is not 16 bytes.

Method apply#

silk
pub fn apply(self: &mut Self, input: &[u8], output: &mut [u8]) -> Result<(), AesError>

Encrypts or decrypts input into the first input.length bytes of output.

The bytes of output after them do not change. input and output must not overlap.

Errors#

  • OutputTooSmall if output is shorter than input. The state does not change.

AesOfb#

silk
pub struct AesOfb

AES in output feedback mode, OFB (NIST SP 800-38A section 6.4).

When to use#

Use it where a protocol names AES-OFB, such as Node's aes-256-ofb. It does not detect changes to the ciphertext. For new designs, use silk.crypto.aes_gcm.

Details#

The 16-byte IV is encrypted again and again to give the key stream, so it does not depend on the data and AesOfb.apply both encrypts and decrypts. The result does not depend on how the data is split. Nothing allocates.

Gotchas#

The same key and IV must never encrypt two messages. If they do, the XOR of the two ciphertexts is the XOR of the two plaintexts. The value holds key material and is not erased when it is dropped.

Implementation AesOfb#

silk
impl AesOfb

Associated function make#

silk
pub fn make(key: &[u8], iv: &[u8]) -> Result<AesOfb, AesError>

Returns an OFB key stream for key and iv.

Errors#

  • InvalidKeyLength if key is not 16, 24, or 32 bytes.

  • InvalidIvLength if iv is not 16 bytes.

Method apply#

silk
pub fn apply(self: &mut Self, input: &[u8], output: &mut [u8]) -> Result<(), AesError>

XORs the next input.length bytes of key stream with input into output.

The bytes of output after them do not change. input and output must not overlap.

Errors#

  • OutputTooSmall if output is shorter than input. The state does not change.

Category: Cryptography.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.

View the source of this page