RustCrypto / RSA

RSA implementation in pure Rust
Apache License 2.0
536 stars 148 forks source link

Deterministic key pair from a seed #209

Closed elielnfinic closed 1 year ago

elielnfinic commented 1 year ago

Hi everybody,

I know that in order to have strong keys, we need to have two random numbers but on the other hand, it is difficult to recover a lost key. Is there a possibility to allow generation of keys based on a deterministic seed as it is done in BIP-32/39 to allow easy recovery of the keys?

Thank you.

elielnfinic commented 1 year ago

I was able to generate deterministic RSA private key using RsaPrivateKey::from_components. So, I am closing this issue.

tarcieri commented 1 year ago

@elielnfinic FYI, if this is helpful:

RsaPrivateKey::new accepts an RngCore + CryptoRng argument.

You can use e.g. ChaCha8Rng for this, which produces a random keystream from a 256-bit seed.

elielnfinic commented 1 year ago

Thanks @tarcieri, I was able to generate deterministic pair with ChaCha8Rng. I am using bip32 library to generate seed from mnemonic but it generates a [u8; 64] though ChaCha8Rng accepts [u8; 32]. Can you suggest any library that outputs [u8; 32] from a mnemonic phrase?

elielnfinic commented 1 year ago

I have been able to overcome this issue by using ChaCha8Rng::seed_from_u64 by hashing the mnemonic to a u64 integer.

#[derive(Hash)]
struct MnemoPhrase{
     phrase : String
}

let hash_ = MnemoPhrase{
  phrase : String::from("wreck mad stand kidney cabin area wheat steak attend fortune aerobic library input puzzle burger hurt draw rice ripple slab object certain total visit")
};

let mut s = DefaultHasher::new();
hash_.hash(&mut s);
let seed_u64 = s.finish();

let mut seed = rand_chacha::ChaCha8Rng::seed_from_u64(seed_u64);

let rsa_ = RsaPrivateKey::new(&mut seed, 256).unwrap();
let str_rsa = rsa_.to_pkcs1_pem(rsa::pkcs8::LineEnding::default()).unwrap().to_string();
println!("{:?}", str_rsa);
tarcieri commented 1 year ago

A u64 has insufficient entropy to be secure. You really need to initialize it with 256-bits/32-bytes entropy.

BIP32 derivation should produce two values: a 32-byte derived secret, and a 32-byte “chain code”. You can use the 32-byte secret as the key, ignoring the “chain code”

elielnfinic commented 1 year ago

I did it, thank you.

let phrase = "wreck mad stand kidney cabin area wheat steak attend fortune aerobic library input puzzle burger hurt draw rice ripple slab object certain total visit";
let mnemonic = super::Mnemonic::new(phrase, Default::default()).unwrap();
let private_key = super::XPrv::new(&seed).unwrap();
let priv_ext = private_key.attrs();
let chain_code:[u8; 32] = priv_ext.chain_code;
let mut seed = rand_chacha::ChaCha8Rng::from_seed(chain_code);

let rsa_ = RsaPrivateKey::new(&mut seed, 256).unwrap();
let str_rsa = rsa_.to_pkcs1_pem(rsa::pkcs8::LineEnding::default()).unwrap().to_string();
 println!("{:?}", str_rsa);
tarcieri commented 1 year ago

@elielnfinic cool.

Small nit: note that the "chain code" is usually used to derive the next level of the hierarchy of keys, with the other half used as key material.

elielnfinic commented 1 year ago

Sure, thank you @tarcieri. I really appreciate.