Skip to content

Commit

Permalink
Document NoiseCipher
Browse files Browse the repository at this point in the history
  • Loading branch information
jchambers committed Feb 29, 2024
1 parent 9d2e41d commit f83c42c
Show file tree
Hide file tree
Showing 2 changed files with 328 additions and 6 deletions.
226 changes: 224 additions & 2 deletions src/main/java/com/eatthepath/noise/component/NoiseCipher.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,45 @@
import javax.annotation.concurrent.ThreadSafe;
import javax.crypto.AEADBadTagException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.Key;

/**
* A Noise cipher is a stateless object that encrypts and decrypts data for use in a Noise protocol. Noise cipher
* implementations must be thread-safe (i.e. calling encryption/decryption methods on different sets of data
* concurrently and from different threads must have no adverse effect).
* <p>
* Noise cipher implementations must operate in AEAD mode, produce a 16-byte AEAD tag when encrypting data, and verify
* a 16-byte AEAD tag when decrypting data.
*/
@ThreadSafe
public interface NoiseCipher {

/**
* Returns the name of this Noise cipher as it would appear in a full Noise protocol name.
*
* @return the name of this Noise cipher as it would appear in a full Noise protocol name
*/
String getName();

/**
* Encrypts the given plaintext using the given key, nonce, and associated data. This method returns a new byte buffer
* sized exactly to contain the resulting ciphertext and AEAD tag.
* <p>
* All {@code plaintext.remaining()} bytes starting at {@code plaintext.position()} are processed. Upon return, the
* plaintext buffer's position will be equal to its limit; its limit will not have changed. If associated data is
* provided, the same is true of the associated data buffer. The returned ciphertext buffer's position will be zero,
* and its limit will be equal to its capacity.
*
* @param key the key with which to encrypt the given plaintext
* @param nonce a nonce, which must be unique for the given key
* @param associatedData the associated data to use when calculating an AEAD tag
* @param plaintext the plaintext to encrypt
*
* @return a new byte buffer containing the resulting ciphertext and AEAD tag
*
* @see #getCiphertextLength(int)
*/
default ByteBuffer encrypt(final Key key,
final long nonce,
@Nullable final ByteBuffer associatedData,
Expand All @@ -31,13 +61,51 @@ default ByteBuffer encrypt(final Key key,
return ciphertext;
}

/**
* Encrypts the given plaintext using the given key, nonce, and associated data. Callers are responsible for ensuring
* that the given ciphertext buffer has enough remaining capacity to hold the resulting ciphertext and AEAD tag.
* <p>
* All {@code plaintext.remaining()} bytes starting at {@code plaintext.position()} are processed. Upon return, the
* plaintext buffer's position will be equal to its limit; its limit will not have changed. If associated data is
* provided, the same will be true of the associated data buffer. The ciphertext buffer's position will have advanced
* by n, where n is the value returned by this method; the ciphertext buffer's limit will not have changed.
* <p>
* Note that the ciphertext and plaintext buffers must be different, but may refer to the same underlying byte array
* to facilitate in-place encryption.
*
* @param key the key with which to encrypt the given plaintext
* @param nonce a nonce, which must be unique for the given key
* @param associatedData the associated data to use when calculating an AEAD tag
* @param plaintext the plaintext to encrypt
* @param ciphertext the buffer into which to write the resulting ciphertext and AEAD tag
*
* @return the number of bytes written into the ciphertext buffer
*
* @throws ShortBufferException if the given ciphertext buffer does not have enough remaining capacity to hold the
* resulting ciphertext and AEAD tag
*
* @see #getCiphertextLength(int)
*/
int encrypt(final Key key,
final long nonce,
@Nullable final ByteBuffer associatedData,
final ByteBuffer plaintext,
final ByteBuffer ciphertext)
throws ShortBufferException;

/**
* Encrypts the given plaintext using the given key, nonce, and associated data. This method returns a new byte array
* sized exactly to contain the resulting ciphertext and AEAD tag.
*
* @param key the key with which to encrypt the given plaintext
* @param nonce a nonce, which must be unique for the given key
* @param associatedData the associated data to use when calculating an AEAD tag
* @param plaintext the plaintext to encrypt
*
* @return a new byte array containing the resulting ciphertext and AEAD tag
*
* @see #getCiphertextLength(int)
*/
default byte[] encrypt(final Key key,
final long nonce,
@Nullable final byte[] associatedData,
Expand All @@ -64,6 +132,36 @@ default byte[] encrypt(final Key key,
return ciphertext;
}

/**
* Encrypts the given plaintext using the given key, nonce, and associated data. Callers are responsible for ensuring
* that the given ciphertext array is large enough to hold the resulting ciphertext and AEAD tag.
* <p>
* Note that the ciphertext and plaintext arrays may refer to the same array, allowing for in-place encryption.
*
* @param key the key with which to encrypt the given plaintext
* @param nonce a nonce, which must be unique for the given key
* @param associatedData a byte array containing the associated data (if any) to be used when encrypting the given
* plaintext; may be {@code null}
* @param aadOffset the position within {@code associatedData} where the associated data starts; ignored if
* {@code associatedData} is {@code null}
* @param aadLength the length of the associated data within {@code associatedData}; ignored if {@code associatedData}
* is {@code null}
* @param plaintext a byte array containing the plaintext to encrypt
* @param plaintextOffset the offset within {@code plaintext} where the plaintext begins
* @param plaintextLength the length of the plaintext within {@code plaintext}
* @param ciphertext a byte array into which to write the ciphertext and AEAD tag from this encryption operation
* @param ciphertextOffset the position within {@code ciphertext} at which to begin writing the ciphertext and AEAD
* tag
*
* @return the number of bytes written into the ciphertext array
*
* @throws ShortBufferException if the ciphertext array (after its offset) is too small to hold the resulting
* ciphertext and AEAD tag
* @throws IndexOutOfBoundsException if the given plaintext length exceeds the length of the plaintext array after its
* offset
*
* @see #getCiphertextLength(int)
*/
int encrypt(final Key key,
final long nonce,
@Nullable final byte[] associatedData,
Expand All @@ -75,6 +173,27 @@ int encrypt(final Key key,
final byte[] ciphertext,
final int ciphertextOffset) throws ShortBufferException;

/**
* Decrypts the given ciphertext and verifies its AEAD tag using the given key, nonce, and associated data. This
* method returns a new {@link ByteBuffer} sized exactly to contain the resulting plaintext. The returned buffer's
* position will be zero, and its limit and capacity will be equal to the plaintext length.
* <p>
* All {@code ciphertext.remaining()} bytes starting at {@code ciphertext.position()} are processed. Upon return, the
* ciphertext buffer's position will be equal to its limit; its limit will not have changed. If associated data is
* provided, the same will be true of the associated data buffer.
*
* @param key the key with which to decrypt the given ciphertext
* @param nonce a nonce, which must be unique for the given key
* @param associatedData the associated data to use when verifying the AEAD tag; may be {@code null}
* @param ciphertext the ciphertext to decrypt
*
* @return a {@code ByteBuffer} containing the resulting plaintext
*
* @throws AEADBadTagException if the AEAD tag in the given ciphertext does not match the calculated value
* @throws IllegalArgumentException if the given ciphertext is too short to contain a valid AEAD tag
*
* @see #getPlaintextLength(int)
*/
default ByteBuffer decrypt(final Key key,
final long nonce,
@Nullable final ByteBuffer associatedData,
Expand All @@ -84,6 +203,7 @@ default ByteBuffer decrypt(final Key key,

try {
decrypt(key, nonce, associatedData, ciphertext, plaintext);
plaintext.rewind();
} catch (final ShortBufferException e) {
// This should never happen for a buffer we control
throw new AssertionError(e);
Expand All @@ -92,13 +212,54 @@ default ByteBuffer decrypt(final Key key,
return plaintext;
}

/**
* Decrypts the given ciphertext and verifies its AEAD tag using the given key, nonce, and associated data. This
* method writes the resulting plaintext into the given {@code plaintext} buffer. Callers are responsible for ensuring
* that the given plaintext buffer has enough remaining capacity to hold the resulting plaintext.
* <p>
* All {@code ciphertext.remaining()} bytes starting at {@code ciphertext.position()} are processed. Upon return, the
* ciphertext buffer's position will be equal to its limit; its limit will not have changed. If associated data is
* provided, the same will be true of the associated data buffer. The plaintext buffer's position will have advanced
* by n, where n is the value returned by this method; the plaintext buffer's limit will not have changed.
*
* @param key the key with which to decrypt the given ciphertext
* @param nonce a nonce, which must be unique for the given key
* @param associatedData the associated data to use when verifying the AEAD tag; may be {@code null}
* @param ciphertext the ciphertext to decrypt
* @param plaintext the buffer into which to write the resulting plaintext
*
* @return the number of bytes written into the {@code plaintext} buffer
*
* @throws AEADBadTagException if the AEAD tag in the given ciphertext does not match the calculated value
* @throws IllegalArgumentException if the given ciphertext is too short to contain a valid AEAD tag
* @throws ShortBufferException if the given plaintext buffer does not have enough remaining capacity to hold the
* resulting plaintext
*
* @see #getPlaintextLength(int)
*/
int decrypt(final Key key,
final long nonce,
@Nullable final ByteBuffer associatedData,
final ByteBuffer ciphertext,
final ByteBuffer plaintext)
throws AEADBadTagException, ShortBufferException;

/**
* Decrypts the given ciphertext and verifies its AEAD tag using the given key, nonce, and associated data. This
* method returns a new byte array sized exactly to contain the resulting plaintext.
*
* @param key the key with which to decrypt the given ciphertext
* @param nonce a nonce, which must be unique for the given key
* @param associatedData the associated data to use when verifying the AEAD tag; may be {@code null}
* @param ciphertext the ciphertext to decrypt
*
* @return a byte array containing the resulting plaintext
*
* @throws AEADBadTagException if the AEAD tag in the given ciphertext does not match the calculated value
* @throws IllegalArgumentException if the given ciphertext is too short to contain a valid AEAD tag
*
* @see #getPlaintextLength(int)
*/
default byte[] decrypt(final Key key,
final long nonce,
@Nullable final byte[] associatedData,
Expand All @@ -125,6 +286,37 @@ default byte[] decrypt(final Key key,
return plaintext;
}

/**
* Decrypts the given ciphertext and verifies its AEAD tag. This writes the resulting plaintext into a provided byte
* array.
* <p>
* Note that {@code ciphertext} and {@code plaintext} may refer to the same byte array, allowing for in-place
* decryption.
*
* @param key the key with which to decrypt the given plaintext
* @param nonce a nonce, which must be unique for the given key
* @param associatedData a byte array containing the associated data (if any) to be used when verifying the AEAD tag
* for the given ciphertext; may be {@code null}
* @param aadOffset the position within {@code associatedData} where the associated data starts; ignored if
* {@code associatedData} is {@code null}
* @param aadLength the length of the associated data within {@code associatedData}; ignored if {@code associatedData}
* is {@code null}
* @param ciphertext a byte array containing the ciphertext and AEAD tag to be decrypted and verified
* @param ciphertextOffset the position within {@code ciphertext} at which to begin reading the ciphertext and AEAD
* tag
* @param ciphertextLength the length of the ciphertext and AEAD tag within {@code ciphertext}
* @param plaintext a byte array into which to write the decrypted plaintext
* @param plaintextOffset the offset within {@code plaintext} where the plaintext begins
*
* @return the number of bytes written to {@code plaintext}
*
* @throws AEADBadTagException if the AEAD tag in the given ciphertext does not match the calculated value
* @throws ShortBufferException if {@code plaintext} is not long enough (after its offset) to contain the resulting
* plaintext
* @throws IllegalArgumentException if the given ciphertext is too short to contain a valid AEAD tag
*
* @see #getPlaintextLength(int)
*/
int decrypt(final Key key,
final long nonce,
@Nullable final byte[] associatedData,
Expand All @@ -136,10 +328,26 @@ int decrypt(final Key key,
final byte[] plaintext,
final int plaintextOffset) throws AEADBadTagException, ShortBufferException;

/**
* Returns the size of a buffer needed to hold the ciphertext produced by encrypting a plaintext of the given length
* (the length of the plaintext plus the length of an AEAD tag).
*
* @param plaintextLength the length of a plaintext
*
* @return the length of the ciphertext that would be produced by encrypting a plaintext of the given length
*/
default int getCiphertextLength(final int plaintextLength) {
return plaintextLength + 16;
}

/**
* Returns the size of a buffer needed to hold the plaintext produced by decrypting a ciphertext of the given length
* (the length of the ciphertext minus the length of the AEAD tag).
*
* @param ciphertextLength the length of a ciphertext
*
* @return the length of the plaintext that would be produced by decrypting a ciphertext of the given length
*/
default int getPlaintextLength(final int ciphertextLength) {
if (ciphertextLength < 16) {
throw new IllegalArgumentException("Ciphertexts must be at least 16 bytes long");
Expand All @@ -148,9 +356,23 @@ default int getPlaintextLength(final int ciphertextLength) {
return ciphertextLength - 16;
}

/**
* Converts an array of bytes into a {@link Key} instance suitable for use with this cipher.
*
* @param keyBytes the raw bytes of the key
*
* @return a {@code Key} suitable for use with this cipher
*/
Key buildKey(byte[] keyBytes);

/**
* Generates a new pseudo-random key as a function of the given key.
*
* @param key the key from which to derive a new key
*
* @return a new pseudo-random key derived from the given key
*/
default Key rekey(final Key key) {
return new SecretKeySpec(encrypt(key, 0xffffffffffffffffL, null, new byte[32]), "RAW");
return buildKey(encrypt(key, 0xffffffffffffffffL, null, new byte[32]));
}
}
Loading

0 comments on commit f83c42c

Please sign in to comment.