Skip to content

Add the ability to increase the number of Feistel Rounds #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

bgrieder
Copy link

@bgrieder bgrieder commented Jan 28, 2023

This is a simple PR to allow increasing the number of Feistel rounds.
It does not change the default FF1 API (that uses 10 rounds) but offers 2 additional FF1 structures

  • FF1fr that takes as a const generic the number of desired Fesitel rounds
  • FF1h that set the number of rounds to 18

The value 18, comes from this cryptanalysis paper: https://eprint.iacr.org/2020/1311.pdf, page 18, last paragraph before the acknowledgments

Comment on lines +247 to +248
/// with an adjustable number of Feistel rounds
pub struct FF1fr<const FEISTEL_ROUNDS: u8, CIPH: BlockCipher> {
Copy link
Owner

@str4d str4d Mar 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per #25 (comment) I do not want to expose the ability to configure an arbitrary number of rounds, as this includes round numbers that are insecure. If/when Rust gains full const generics, I would consider reopening this, because then we could enforce e.g. FEISTEL_ROUNDS >= 10. But for now, this should not be part of the public API:

Suggested change
/// with an adjustable number of Feistel rounds
pub struct FF1fr<const FEISTEL_ROUNDS: u8, CIPH: BlockCipher> {
/// with an adjustable number of Feistel rounds.
struct FF1Core<const FEISTEL_ROUNDS: u8, CIPH: BlockCipher> {

Copy link
Contributor

@daira daira Apr 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do this?

/// Non-standard (in general) FPE algorithm based on FF1 but with an adjustable number
/// of Feistel rounds.
///
/// The number of Feistel rounds will be `10 + EXTRA_ROUNDS_ABOVE_10`. Apart from that
/// modification, this follows the FF1 algorithm as specified in [NIST SP 800-38G revision 1],
/// with a minimum domain size of $\mathsf{radix}^\mathsf{minlen} \geq 1,000,000$.
///
/// [NIST Special Publication 800-38G]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf
pub struct FF1WithExtraRounds<const EXTRA_ROUNDS_ABOVE_10: u8, CIPH: BlockCipher> { ... }

/// A struct for performing FF1 encryption and decryption operations.
///
/// This implements FF1 as specified in [NIST SP 800-38G revision 1], with 10 Feistel
/// rounds and a minimum domain size of $\mathsf{radix}^\mathsf{minlen} \geq 1,000,000$.
///
/// [NIST Special Publication 800-38G]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf
pub type FF1<CIPH> = FF1WithExtraRounds<0, CIPH>;

/// Non-standard FPE algorithm that modifies FF1 to use 18 Feistel rounds.
///
/// Other than the modification to the number of Feistel rounds, this follows the FF1
/// algorithm as specified in [NIST SP 800-38G revision 1], with a minimum domain size of
/// $\mathsf{radix}^\mathsf{minlen} \geq 1,000,000$.
///
/// [NIST Special Publication 800-38G]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf
pub type FF1R18<CIPH> = FF1WithExtraRounds<8, CIPH>;

(poor man's range typing!)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's no way to raise the floor of this bound later if NIST devices to replace FF1 with a higher-round version. So for now I'd prefer to only expose explicit round versions.

/// A struct for performing hardened FF1 encryption and decryption operations
/// using 18 Feistel rounds
pub type FF1h<CIPH> = FF1fr<18, CIPH>;

/// A struct for performing FF1 encryption and decryption operations.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A struct for performing FF1 encryption and decryption operations.
/// A struct for performing FF1 encryption and decryption operations

Comment on lines +238 to +239
/// A struct for performing FF1 encryption and decryption operations
/// using the default 10 Feistel rounds
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A struct for performing FF1 encryption and decryption operations
/// using the default 10 Feistel rounds
/// A struct for performing FF1 encryption and decryption operations.
///
/// This implements FF1 as specified in [NIST SP 800-38G revision 1], with 10 Feistel
/// rounds and a minimum domain size of $\mathsf{radix}^\mathsf{minlen} \geq 1,000,000$.
///
/// [NIST Special Publication 800-38G]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf

@@ -232,24 +235,35 @@ fn generate_s<'a, CIPH: BlockEncrypt>(
.take(d)
}

/// A struct for performing FF1 encryption and decryption operations
/// using the default 10 Feistel rounds
pub type FF1<CIPH> = FF1fr<10, CIPH>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub type FF1<CIPH> = FF1fr<10, CIPH>;
pub type FF1<CIPH> = FF1Core<10, CIPH>;

ciph: CIPH,
radix: Radix,
}

impl<CIPH: BlockCipher + KeyInit> FF1<CIPH> {
impl<const FEISTEL_ROUNDS: u8, CIPH: BlockCipher + KeyInit> FF1fr<FEISTEL_ROUNDS, CIPH> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
impl<const FEISTEL_ROUNDS: u8, CIPH: BlockCipher + KeyInit> FF1fr<FEISTEL_ROUNDS, CIPH> {
impl<const FEISTEL_ROUNDS: u8, CIPH: BlockCipher + KeyInit> FF1Core<FEISTEL_ROUNDS, CIPH> {

/// Creates a new FF1 object for the given key and radix.
///
/// Returns an error if the given radix is not in [2..2^16].
pub fn new(key: &[u8], radix: u32) -> Result<Self, InvalidRadix> {
let ciph = CIPH::new(GenericArray::from_slice(key));
let radix = Radix::from_u32(radix)?;
Ok(FF1 { ciph, radix })
Ok(FF1fr { ciph, radix })
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Ok(FF1fr { ciph, radix })
Ok(Self { ciph, radix })

}
}

impl<CIPH: BlockCipher + BlockEncrypt + Clone> FF1<CIPH> {
impl<const FEISTEL_ROUNDS: u8, CIPH: BlockCipher + BlockEncrypt + Clone>
FF1fr<FEISTEL_ROUNDS, CIPH>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
FF1fr<FEISTEL_ROUNDS, CIPH>
FF1Core<FEISTEL_ROUNDS, CIPH>

Comment on lines +242 to +244
/// A struct for performing hardened FF1 encryption and decryption operations
/// using 18 Feistel rounds
pub type FF1h<CIPH> = FF1fr<18, CIPH>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically no longer FF1, because the number of rounds is part of the FF1 specification. So I do not want to describe it as "hardened FF1", not least because any future research breakthroughs could lead to further weakening such that 18 rounds also becomes insufficient.

NIST faced a similar issue with FF3, the original version of which was broken in a way that couldn't be fixed by changing bounds. They named the updated version FF3-1, suggesting this is likely how they would also name subsequent updates. If an FF1-1 were published, I'd name it FF1_1 (underscores are allowed in struct names under Rust naming conventions when separating integers). In the interest of not colliding with this, I propose that we name this increased-round modification :

Suggested change
/// A struct for performing hardened FF1 encryption and decryption operations
/// using 18 Feistel rounds
pub type FF1h<CIPH> = FF1fr<18, CIPH>;
/// Non-standard FPE algorithm that modifies FF1 to use 18 Feistel rounds.
///
/// Other than the modification to the number of Feistel rounds, this follows the FF1
/// algorithm as specified in [NIST SP 800-38G revision 1], with a minimum domain size of
/// $\mathsf{radix}^\mathsf{minlen} \geq 1,000,000$.
///
/// [NIST Special Publication 800-38G]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf
pub type FF1R18<CIPH> = FF1Core<18, CIPH>;

@@ -0,0 +1,19 @@
use aes::Aes256;

use crate::ff1::{BinaryNumeralString, FF1h};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
use crate::ff1::{BinaryNumeralString, FF1h};
use crate::ff1::{BinaryNumeralString, FF1R18};

let radix = 2;
let pt = [0xab, 0xcd, 0xef];

let ff = FF1h::<Aes256>::new(&key, radix).unwrap();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let ff = FF1h::<Aes256>::new(&key, radix).unwrap();
let ff = FF1R18::<Aes256>::new(&key, radix).unwrap();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants