Closed hwiorn closed 3 years ago
miscreant.rs does not support Aes512Cmac now
in miscreant.rs
, the cipher AEAD_AES_SIV_CMAC_512
is called Aes256SivAead
. but crypto2
is called AesSivCmac512
.
Test Code:
extern crate hex;
extern crate crypto2;
extern crate miscreant;
use crypto2::aeadcipher::AesSivCmac512;
fn siv_encrypt(key: &[u8], plaintext: &[u8], py_ciphertext: &[u8]) {
println!("================ SIV ENC ====================");
println!("AES-SIV-CMAC-512 Key: {:?}", hex::encode(&key));
println!(" Plaintext: {:?}", hex::encode(&plaintext));
println!();
// python miscreant AES-SIV-CMAC-512
// https://github.com/miscreant/miscreant.py
{
println!("Python Miscreant result: {:?}", hex::encode(&py_ciphertext));
}
println!();
// miscreant AES-SIV-CMAC-512
{
use miscreant::Aead;
use miscreant::Aes256SivAead;
let mut cipher = Aes256SivAead::new(&key);
let nonce: [u8; 0] = [];
let aad: [u8; 0] = [];
let mut ciphertext = vec![0u8; AesSivCmac512::TAG_LEN]; // V || P(C)
ciphertext.extend_from_slice(&plaintext);
cipher.encrypt_in_place(&nonce, &aad, &mut ciphertext);
println!(" Miscreant result: {:?}", hex::encode(&ciphertext));
}
println!();
// crypto2 AES-SIV-CMAC-512
{
let cipher = AesSivCmac512::new(&key);
let nonce: [u8; 0] = [];
let aad: [u8; 0] = [];
let mut ciphertext = vec![0u8; AesSivCmac512::TAG_LEN]; // V || P(C)
ciphertext.extend_from_slice(&plaintext);
let components: &[&[u8]] = &[];
cipher.encrypt_slice(&components, &mut ciphertext);
println!(" Crypto2 result: {:?}", hex::encode(&ciphertext));
let mut ciphertext = vec![0u8; AesSivCmac512::TAG_LEN]; // V || P(C)
ciphertext.extend_from_slice(&plaintext);
let components: &[&[u8]] = &[&aad];
cipher.encrypt_slice(&components, &mut ciphertext);
println!(" Crypto2 result: {:?}", hex::encode(&ciphertext));
let mut ciphertext = vec![0u8; AesSivCmac512::TAG_LEN]; // V || P(C)
ciphertext.extend_from_slice(&plaintext);
let components: &[&[u8]] = &[&aad, &nonce];
cipher.encrypt_slice(&components, &mut ciphertext);
println!(" Crypto2 result: {:?}", hex::encode(&ciphertext));
}
println!();
}
fn main() {
let key: [u8; AesSivCmac512::KEY_LEN] = [
210, 74, 211, 191, 98, 164, 14, 238, 213, 192, 46, 64, 133, 107, 253, 41,
115, 180, 47, 142, 210, 48, 204, 114, 122, 55, 36, 204, 135, 225, 178, 130,
232, 50, 179, 61, 118, 166, 202, 75, 10, 83, 158, 144, 212, 124, 251, 51,
71, 61, 233, 200, 200, 21, 19, 53, 41, 66, 242, 96, 230, 20, 194, 195,
];
let plaintext = "".as_bytes(); // empty plaintext
let py_ciphertext = [248, 84, 15, 233, 58, 91, 208, 105, 201, 101, 161, 127, 136, 242, 21, 134];
siv_encrypt(&key, &plaintext, &py_ciphertext);
let plaintext = "ff3bb8f2-dd51-4ac6-9c79-cb0ab79c23e5".as_bytes();
let py_ciphertext = [
86, 67, 223, 72, 65, 160, 109, 83, 86, 81, 231, 36, 195, 68, 40, 120, 43, 219, 25,
194, 233, 134, 143, 212, 219, 19, 187, 159, 110, 229, 109, 111, 150, 57, 230, 50,
24, 120, 210, 146, 178, 254, 204, 20, 31, 10, 22, 128, 97, 143, 172, 74
];
siv_encrypt(&key, &plaintext, &py_ciphertext);
}
Result:
================ SIV ENC ==================== AES-SIV-CMAC-512 Key: "d24ad3bf62a40eeed5c02e40856bfd2973b42f8ed230cc727a3724cc87e1b282e832b33d76a6ca4b0a539e90d47cfb33473de9c8c81513352942f260e614c2c3" Plaintext: ""
Python Miscreant result: "f8540fe93a5bd069c965a17f88f21586"
Miscreant result: "f1423fb8d115bdc05a056db221ecd4c7"
Crypto2 result: "8290092e8208c6a7fc317ae0c1d4eb03"
Crypto2 result: "307386bf3b8659c1b346738a29ce071e"
Crypto2 result: "f1423fb8d115bdc05a056db221ecd4c7"
================ SIV ENC ==================== AES-SIV-CMAC-512 Key: "d24ad3bf62a40eeed5c02e40856bfd2973b42f8ed230cc727a3724cc87e1b282e832b33d76a6ca4b0a539e90d47cfb33473de9c8c81513352942f260e614c2c3" Plaintext: "66663362623866322d646435312d346163362d396337392d636230616237396332336535"
Python Miscreant result: "5643df4841a06d535651e724c34428782bdb19c2e9868fd4db13bb9f6ee56d6f9639e6321878d292b2fecc141f0a1680618fac4a"
Miscreant result: "aaaacb6cb8ccc0ebb053d78ea5d8bb907dbf6c65e9e842c46f26249b08da5c547675d27b52ce1f6a99c2b212688753c964d37e77"
Crypto2 result: "5643df4841a06d535651e724c34428782bdb19c2e9868fd4db13bb9f6ee56d6f9639e6321878d292b2fecc141f0a1680618fac4a"
Crypto2 result: "4fcc56329c6a6eea569dbc65dcc880e3efdec99ee781449f655adff1ff2f53d6b579ccd75b9f583a57a5dc265139a8412c02ebcf"
Crypto2 result: "aaaacb6cb8ccc0ebb053d78ea5d8bb907dbf6c65e9e842c46f26249b08da5c547675d27b52ce1f6a99c2b212688753c964d37e77"
If you set aad
and nonce
correctly, u can ses the crypto2
result is same rust-miscreant
and python-miscreant
.
There is only one exception, that is, when you encrypt a empty plaintext, the result will be different. That is because the implementation of python-miscreant is wrong. See the note here. The implementation of rust-miscreant and crypto2 is correct.
There is only one exception, that is, when you encrypt a empty plaintext, the result will be different.
Wow, I didn't know that. Thanks for your answer and the pieces of code.
Actually, my python script was written in Java at first and I used the Cryptomator siv-mode library. Both siv-mode library and miscreant-py module get same encrypted results when I put an empty plaintext. That's why I thought miscreant-py was right. And I guess the developers of the Cryptomator deal with an empty plaintext as a valid input because of Cryptomator Architecture.
The directory ID for the root directory is the empty string. For all other directories, it is a random sequence of at most 36 ASCII chars. We recommend using random UUID.
I found the same comment in the siv-mode library and the related issue #14. n>0
of the note seems to explain the plaintext always be a last vector, not zero-length vector. So, S2V should process a last zero-length vector(empty plaintext). If I am wrong, please correct me.
And I'm confusing a little. You mean, miscreant-py did implement wrong encryption when got an empty plaintext? In Crypto2, an empty plaintext makes different results and Is the result right?
Test Java code:
import org.cryptomator.siv.SivMode;
public class CryptomatorSivMode {
private static final SivMode AES_SIV = new SivMode();
public static void main(String[] args) {
byte[] ctrKey = {(byte)232, (byte)50, (byte)179, (byte)61, (byte)118, (byte)166, (byte)202, (byte)75, (byte)10, (byte)83, (byte)158, (byte)144, (byte)212, (byte)124, (byte)251, (byte)51, (byte)71, (byte)61, (byte)233, (byte)200, (byte)200, (byte)21, (byte)19, (byte)53, (byte)41, (byte)66, (byte)242, (byte)96, (byte)230, (byte)20, (byte)194, (byte)195};
byte[] macKey = {(byte)210, (byte)74, (byte)211, (byte)191, (byte)98, (byte)164, (byte)14, (byte)238, (byte)213, (byte)192, (byte)46, (byte)64, (byte)133, (byte)107, (byte)253, (byte)41, (byte)115, (byte)180, (byte)47, (byte)142, (byte)210, (byte)48, (byte)204, (byte)114, (byte)122, (byte)55, (byte)36, (byte)204, (byte)135, (byte)225, (byte)178, (byte)130};
byte[] encrypted = null;
encrypted = AES_SIV.encrypt(ctrKey, macKey, "".getBytes());
for(byte b : encrypted) System.out.print(String.format("%d, ", (int) b & 0xFF));
System.out.println();
for(byte b : encrypted) System.out.print(String.format("%02X", (int) b & 0xFF));
}
}
Results:
248, 84, 15, 233, 58, 91, 208, 105, 201, 101, 161, 127, 136, 242, 21, 134,
F8540FE93A5BD069C965A17F88F21586
Hmm, miscreant's rust version also produces the same result as crypto2.
Follow the RFC-5297
the plaintext is
P
, the associated data isAD1
throughADn
,V
is the syntheticIV
, the ciphertext isC
, andZ
is the output.
Algorithmically, SIV Encrypt can be described as:
SIV-ENCRYPT(K, P, AD1, ..., ADn) {
K1 = leftmost(K, len(K)/2)
K2 = rightmost(K, len(K)/2)
V = S2V(K1, AD1, ..., ADn, P)
...
}
S2V with key K on a vector of n inputs S1, S2, ..., Sn-1, Sn, and len(Sn) < 128:
Algorithmically S2V can be described as:
S2V(K, S1, ..., Sn) {
if n = 0 then
# The code here leads to different calculation results
return V = AES-CMAC(K, <one>)
fi
...
}
When you try to encrypt a empty
plaintext without AD
and NONCE
,
the miscreant's python implementation (also the Java implementation you mentioned) thinks N
is not equal to 0
, while my implementation thinks N
should be equal to 0
.
The RFC-5297 does not clearly define this issue, and i personally do not take a position on this issue.
I saw google/wycheproof collected this case.
and the test vectors shows that my implementation is correct :)
Test Code:
#[test]
fn test_aes_siv_cmac_256_wycheproof_t2() {
// https://github.com/google/wycheproof/blob/master/testvectors/aes_siv_cmac_test.json#L29-L36
let key = hex::decode("2b27e429fb6c02678e589ccc4437c5adfb44b331ab6d21ea321727e6ec03d354").unwrap();
let aad: [u8; 0] = [];
let plaintext: [u8; 0] = [];
let cipher = AesSivCmac256::new(&key);
let mut ciphertext = vec![0u8; AesSivCmac256::TAG_LEN];
ciphertext.extend_from_slice(&plaintext);
cipher.encrypt_slice(&[&aad], &mut ciphertext);
assert_eq!(&ciphertext[..],
&hex::decode("b2b2354e3724dcdaa85ecf029b49a90c").unwrap()[..]);
}
#[test]
fn test_aes_siv_cmac_256_wycheproof_t3() {
// https://github.com/google/wycheproof/blob/master/testvectors/aes_siv_cmac_test.json#L39-L46
let key = hex::decode("e40992eb4f649e5d49134652aecc24bafa6b45ce8dd9e9d371ede7d5de84fa72").unwrap();
let aad = hex::decode("8268c5194a71aed0fc1dafe3").unwrap();
let plaintext: [u8; 0] = [];
let cipher = AesSivCmac256::new(&key);
let mut ciphertext = vec![0u8; AesSivCmac256::TAG_LEN];
ciphertext.extend_from_slice(&plaintext);
cipher.encrypt_slice(&[&aad], &mut ciphertext);
assert_eq!(&ciphertext[..],
&hex::decode("92bc07ee200fbd488b7f70a10da26a21").unwrap()[..]);
}
BTW:
the google/wycheproof test vectors is widely used in different cryptographic libraries, For example:
Thanks for response.
the miscreant's python implementation (also the Java implementation you mentioned) thinks N is not equal to 0, while my implementation thinks N should be equal to 0.
The RFC-5297 does not clearly define this issue, and i personally do not take a position on this issue.
I agree your opinion :)
I already tested miscreant.go
and it's the same as Crypto2. I got to know miscreant.py
makes the different result when I put a zero-length vector.
In addition, miscreant.rs
just uses aes-siv
to encrypt and decrypt messages. I saw its test-vector and they already has been testing it.
I think I have to implement own encryption and decryption code for an empty input using Crypto2. Could you give me some hints instead of coping-and-paste whole AesSiv of Crypto2?
@hwiorn
I think I have to implement own encryption and decryption code for an empty input using Crypto2. Could you give me some hints instead of coping-and-paste whole AesSiv of Crypto2?
I can add a special function to deal with this problem, but I haven't thought of a name yet.
I can add a special function to deal with this problem, but I haven't thought of a name yet.
Wow! Thankfully, I can migrate rest my code to Rust until you add the function. Please let me know. I'll wait for the update :)
@hwiorn See commit https://github.com/shadowsocks/crypto2/commit/9de975382a4ab142d92aba4ac25fb18412394cd4
// Default encrypt
cipher.encrypt_slice(&[], &mut ciphertext); // not ignore empty
// With option
let ignore_empty = true;
cipher.encrypt_slice_opt(&[], &mut ciphertext, ignore_empty);
I would recommend:
struct EncryptOpts {
ignore_empty: bool,
}
impl Default for EncryptOpts {
fn default() -> EncryptOpts {
EncryptOpts {
ignore_empty: false
}
}
}
let opt = EncryptOpts {
ignore_empty: true
};
cipher.encrypt_slice_opt(&[], &mut ciphertext, &opt);
@zonyitoo
See commit https://github.com/shadowsocks/crypto2/commit/773d32d4822ff0e13c8cd86c6ad44b1afa0c6cc8
// Default encrypt
cipher.encrypt_slice(&[], &mut ciphertext); // not ignore empty
// With option
let opts = AesSivCmacOpts { ignore_empty: true };
cipher.encrypt_slice_opt(&[], &mut ciphertext, &opts);
@LuoZijun
I tested a SIV decryption when plaintext is greater than AesSivCmac512::BLOCK_LEN
and got an assertion failure.
Can it be the same problem? Could you give me advice?
{
let key = hex::decode("d24ad3bf62a40eeed5c02e40856bfd2973b42f8ed230cc727a3724cc87e1b282\
e832b33d76a6ca4b0a539e90d47cfb33473de9c8c81513352942f260e614c2c3").unwrap();
let plaintext = hex::decode("65c2c7afe890092fe7515260637d9fe01b74699022564f79").unwrap();
let cipher = AesSivCmac512::new(&key);
let mut ciphertext = vec![0u8; AesSivCmac512::TAG_LEN];
ciphertext.extend_from_slice(&plaintext);
// let components: &[&[u8]] = &[];
cipher.decrypt_slice(&[], &mut ciphertext);
}
thread 'main' panicked at 'assertion failed: m.len() >= Self::BLOCK_LEN', /home/gglee/.cargo/registry/src/github.com-1ecc6299db9ec823/crypto2-0.1.1/src/blockmode/siv.rs:394:1
Same python code using miscreant.py:
from miscreant.aes.siv import SIV
from binascii import unhexlify, hexlify
plaintext = unhexlify('65c2c7afe890092fe7515260637d9fe01b74699022564f79')
siv = SIV(unhexlify('d24ad3bf62a40eeed5c02e40856bfd2973b42f8ed230cc727a3724cc87e1b282e832b33d76a6ca4b0a539e90d47cfb33473de9c8c81513352942f260e614c2c3'))
print(hexlify(siv.open(plaintext, associated_data=[b''])))
result:
b'746573742e747874'
@hwiorn
The debugging assertion is indeed incorrect, but it is not related to the problem you are encountering.
let key = hex::decode("d24ad3bf62a40eeed5c02e40856bfd2973b42f8ed230cc727a3724cc87e1b282\
e832b33d76a6ca4b0a539e90d47cfb33473de9c8c81513352942f260e614c2c3").unwrap();
let mut ciphertext = hex::decode("65c2c7afe890092fe7515260637d9fe01b74699022564f79").unwrap();
let components: &[&[u8]] = &[&[]]; // NOTE: same as your python args.
let cipher = AesSivCmac512::new(&key);
let ret = cipher.decrypt_slice(&components, &mut ciphertext);
assert_eq!(ret, true);
let cleartext = &ciphertext[AesSivCmac512::TAG_LEN..]; // V | P
assert_eq!(cleartext, &hex::decode("746573742e747874").unwrap()[..]);
Ah.. I don't have to extend TAG size in ciphertext.... I noticed that just now....
let components: &[&[u8]] = &[&[]]; // NOTE: same as your python args.
Actually before I wrote the comment, yesterday I already tried every components
arg you mentioned before, but no luck. Because of invalid decryption code.
I see my code was the problem. Thank you!
I use miscreant.aes.siv in my python script and I'm trying to migrate it to Rust using Crypto2 AES SIV. (miscreant.rs does not support Aes512Cmac now)
Mac and Key are 32bit. So I need the Aes512Cmac. But, It's not the same results. I think my rust code is something wrong. I tried to figure it out and find it in the Crypto2 documents, but I couldn't. How can I get the same results correctly like the miscreant-py?
Python test script
Rust test code