Encryption! With test cases.

This commit is contained in:
2018-05-02 17:05:17 -07:00
parent bd0ddd848b
commit fa04efa5fe
11 changed files with 8974 additions and 7 deletions

View File

@@ -18,6 +18,7 @@ extern crate quickcheck;
extern crate rand;
extern crate sha1;
extern crate sha2;
extern crate simple_asn1;
/// The cryptonum module provides support for large numbers for use in various
/// cryptographically-relevant algorithms.

View File

@@ -132,6 +132,9 @@ pub fn pkcs1_pad(ident: &[u8], hash: &[u8], keylen: usize) -> Vec<u8> {
result
}
pub fn xor_vecs(a: &Vec<u8>, b: &Vec<u8>) -> Vec<u8> {
a.iter().zip(b.iter()).map(|(a,b)| a^b).collect()
}
#[cfg(test)]
mod tests {

View File

@@ -1,3 +1,4 @@
use simple_asn1::ASN1DecodeErr;
use std::io;
#[derive(Debug)]
@@ -11,4 +12,27 @@ impl From<io::Error> for RSAKeyGenError {
}
}
#[derive(Debug)]
pub enum RSAError {
BadMessageSize,
KeyTooSmallForHash,
DecryptionError,
DecryptHashMismatch,
InvalidKey,
RandomGenError(io::Error),
ASN1DecodeErr(ASN1DecodeErr)
}
impl From<io::Error> for RSAError {
fn from(e: io::Error) -> RSAError {
RSAError::RandomGenError(e)
}
}
impl From<ASN1DecodeErr> for RSAError {
fn from(e: ASN1DecodeErr) -> RSAError {
RSAError::ASN1DecodeErr(e)
}
}

View File

@@ -1,4 +1,7 @@
use rsa::*;
use rsa::oaep::OAEPParams;
use sha1::Sha1;
use sha2::{Sha224,Sha256,Sha384,Sha512};
use testing::run_test;
fn get_signing_hash(s: usize) -> &'static SigningHash {
@@ -56,3 +59,75 @@ fn rsa_verification_tests()
assert!(key.verify(hash, &msg, sig));
});
}
#[test]
fn rsa_decryption_tests()
{
run_test("tests/rsa/encryption.test", 6, |case| {
let (neg1, dbytes) = case.get("d").unwrap();
let (neg2, nbytes) = case.get("n").unwrap();
let (neg3, hbytes) = case.get("h").unwrap();
let (neg4, lbytes) = case.get("l").unwrap();
let (neg5, msg) = case.get("m").unwrap();
let (neg6, cphtxt) = case.get("c").unwrap();
assert!(!neg1 & !neg2 & !neg3 & !neg4 & !neg5 & !neg6);
let label = String::from_utf8(lbytes.clone()).unwrap();
let key = RSAPrivate::new(UCN::from_bytes(nbytes),
UCN::from_bytes(dbytes));
let wrapped = match usize::from(UCN::from_bytes(hbytes)) {
0x1 => key.decrypt(&OAEPParams::new(Sha1::default(), label),cphtxt),
0x224 => key.decrypt(&OAEPParams::new(Sha224::default(),label),cphtxt),
0x256 => key.decrypt(&OAEPParams::new(Sha256::default(),label),cphtxt),
0x384 => key.decrypt(&OAEPParams::new(Sha384::default(),label),cphtxt),
0x512 => key.decrypt(&OAEPParams::new(Sha512::default(),label),cphtxt),
_ => panic!("Unacceptable hash")
};
let mymsg = wrapped.unwrap();
assert_eq!(msg, &mymsg);
});
}
#[test]
fn rsa_encryption_tests()
{
run_test("tests/rsa/encryption.test", 6, |case| {
let (neg1, dbytes) = case.get("d").unwrap();
let (neg2, nbytes) = case.get("n").unwrap();
let (neg3, hbytes) = case.get("h").unwrap();
let (neg4, lbytes) = case.get("l").unwrap();
let (neg5, msg) = case.get("m").unwrap();
// This one's a little tricky, because there's randomness in the
// encryption phase. So we can't just encrypt and see if we get the
// same value. Instead, we just use this as a test vector to round
// trip, and trust that the decryption test above makes sure we're
// not going off into la la land.
assert!(!neg1 & !neg2 & !neg3 & !neg4 & !neg5);
let label = String::from_utf8(lbytes.clone()).unwrap();
let private = RSAPrivate::new(UCN::from_bytes(nbytes),
UCN::from_bytes(dbytes));
let public = RSAPublic::new(UCN::from_bytes(nbytes),
UCN::from(65537u64));
let wrappedc = match usize::from(UCN::from_bytes(hbytes)) {
0x1 => public.encrypt(&OAEPParams::new(Sha1::default(), label.clone()), &msg),
0x224 => public.encrypt(&OAEPParams::new(Sha224::default(),label.clone()), &msg),
0x256 => public.encrypt(&OAEPParams::new(Sha256::default(),label.clone()), &msg),
0x384 => public.encrypt(&OAEPParams::new(Sha384::default(),label.clone()), &msg),
0x512 => public.encrypt(&OAEPParams::new(Sha512::default(),label.clone()), &msg),
_ => panic!("Unacceptable hash")
};
let ciphertext = wrappedc.unwrap();
let wrappedm = match usize::from(UCN::from_bytes(hbytes)) {
0x1 => private.decrypt(&OAEPParams::new(Sha1::default(), label), &ciphertext),
0x224 => private.decrypt(&OAEPParams::new(Sha224::default(),label), &ciphertext),
0x256 => private.decrypt(&OAEPParams::new(Sha256::default(),label), &ciphertext),
0x384 => private.decrypt(&OAEPParams::new(Sha384::default(),label), &ciphertext),
0x512 => private.decrypt(&OAEPParams::new(Sha512::default(),label), &ciphertext),
_ => panic!("Unacceptable hash")
};
let message = wrappedm.unwrap();
assert_eq!(msg, &message);
});
}

View File

@@ -2,6 +2,7 @@ mod core;
mod errors;
#[cfg(test)]
mod gold_tests;
mod oaep;
mod public;
mod private;
mod signing_hashes;

47
src/rsa/oaep.rs Normal file
View File

@@ -0,0 +1,47 @@
use cryptonum::UCN;
use digest::{FixedOutput,Input};
/// Parameters for OAEP encryption and decryption: a hash function to use as part of the message
/// generation function (MGF1, if you're curious), and any labels you want to include as part of
/// the encryption.
pub struct OAEPParams<H: Clone + Input + FixedOutput> {
pub hash: H,
pub label: String
}
impl<H: Clone + Input + FixedOutput> OAEPParams<H> {
pub fn new(hash: H, label: String)
-> OAEPParams<H>
{
OAEPParams { hash: hash, label: label }
}
pub fn hash_len(&self) -> usize {
self.hash.clone().fixed_result().as_slice().len()
}
pub fn hash(&self, input: &[u8]) -> Vec<u8> {
let mut digest = self.hash.clone();
digest.process(input);
digest.fixed_result().as_slice().to_vec()
}
pub fn mgf1(&self, input: &[u8], len: usize) -> Vec<u8> {
let mut res = Vec::with_capacity(len);
let mut counter = UCN::from(0u64);
let one = UCN::from(1u64);
while res.len() < len {
let c = counter.to_bytes(4);
let mut digest = self.hash.clone();
digest.process(input);
digest.process(&c);
let chunk = digest.fixed_result();
res.extend_from_slice(chunk.as_slice());
counter = counter + &one;
}
res.truncate(len);
res
}
}

View File

@@ -1,5 +1,8 @@
use cryptonum::{BarrettUCN,UCN};
use rsa::core::{ACCEPTABLE_KEY_SIZES,pkcs1_pad,sp1};
use digest::{FixedOutput,Input};
use rsa::core::{ACCEPTABLE_KEY_SIZES,dp,pkcs1_pad,sp1,xor_vecs};
use rsa::errors::RSAError;
use rsa::oaep::OAEPParams;
use rsa::signing_hashes::SigningHash;
use std::fmt;
@@ -71,4 +74,76 @@ impl RSAPrivate {
let sig = s.to_bytes(self.byte_len);
sig
}
/// Decrypt a message with the given parameters.
pub fn decrypt<H: Clone + Input + FixedOutput>(&self, oaep: &OAEPParams<H>, msg: &[u8])
-> Result<Vec<u8>,RSAError>
{
let mut res = Vec::new();
for chunk in msg.chunks(self.byte_len) {
let mut dchunk = self.oaep_decrypt(oaep, chunk)?;
res.append(&mut dchunk);
}
Ok(res)
}
fn oaep_decrypt<H: Clone + Input + FixedOutput>(&self, oaep: &OAEPParams<H>, c: &[u8])
-> Result<Vec<u8>,RSAError>
{
// Step 1b
if c.len() != self.byte_len {
return Err(RSAError::DecryptionError);
}
// Step 1c
if self.byte_len < ((2 * oaep.hash_len()) + 2) {
return Err(RSAError::DecryptHashMismatch);
}
// Step 2a
let c_ip = UCN::from_bytes(&c.to_vec());
// Step 2b
let m_ip = dp(&self.nu, &self.d, &c_ip);
// Step 2c
let em = &m_ip.to_bytes(self.byte_len);
// Step 3a
let l_hash = oaep.hash(oaep.label.as_bytes());
// Step 3b
let (y, rest) = em.split_at(1);
let (masked_seed, masked_db) = rest.split_at(oaep.hash_len());
// Step 3c
let seed_mask = oaep.mgf1(masked_db, oaep.hash_len());
// Step 3d
let seed = xor_vecs(&masked_seed.to_vec(), &seed_mask);
// Step 3e
let db_mask = oaep.mgf1(&seed, self.byte_len - oaep.hash_len() - 1);
// Step 3f
let db = xor_vecs(&masked_db.to_vec(), &db_mask);
// Step 3g
let (l_hash2, ps_o_m) = db.split_at(oaep.hash_len());
let o_m = drop0s(ps_o_m);
let (o, m) = o_m.split_at(1);
// Checks!
if o != [1] {
return Err(RSAError::DecryptionError);
}
if l_hash != l_hash2 {
return Err(RSAError::DecryptionError);
}
if y != [0] {
return Err(RSAError::DecryptionError);
}
Ok(m.to_vec())
}
}
fn drop0s(a: &[u8]) -> &[u8] {
let mut idx = 0;
while (idx < a.len()) && (a[idx] == 0) {
idx = idx + 1;
}
&a[idx..]
}

View File

@@ -1,5 +1,9 @@
use cryptonum::{BarrettUCN,UCN};
use rsa::core::{ACCEPTABLE_KEY_SIZES,pkcs1_pad,vp1};
use digest::{FixedOutput,Input};
use rand::{OsRng,Rng};
use rsa::core::{ACCEPTABLE_KEY_SIZES,ep,pkcs1_pad,vp1,xor_vecs};
use rsa::errors::RSAError;
use rsa::oaep::OAEPParams;
use rsa::signing_hashes::SigningHash;
#[derive(Clone,Debug,PartialEq,Eq)]
@@ -39,4 +43,82 @@ impl RSAPublic {
let em_ = pkcs1_pad(&shash.ident, &hash, self.byte_len);
(em == em_)
}
/// Encrypt the given data with the public key and parameters, returning
/// the encrypted blob or an error encountered during encryption.
///
/// OAEP encoding is used for this process, which requires a random number
/// generator. This version of the function uses `OsRng`. If you want to
/// use your own RNG, use `encrypt_w_rng`.
pub fn encrypt<H:Clone + Input + FixedOutput>(&self, oaep: &OAEPParams<H>, msg: &[u8])
-> Result<Vec<u8>,RSAError>
{
let mut g = OsRng::new()?;
self.encrypt_with_rng(&mut g, oaep, msg)
}
/// Encrypt the given data with the public key and parameters, returning
/// the encrypted blob or an error encountered during encryption. This
/// version also allows you to provide your own RNG, if you really feel
/// like shooting yourself in the foot.
pub fn encrypt_with_rng<G,H>(&self, g: &mut G, oaep: &OAEPParams<H>, msg: &[u8])
-> Result<Vec<u8>,RSAError>
where G: Rng, H: Clone + Input + FixedOutput
{
if self.byte_len <= ((2 * oaep.hash_len()) + 2) {
return Err(RSAError::KeyTooSmallForHash);
}
let mut res = Vec::new();
for chunk in msg.chunks(self.byte_len - (2 * oaep.hash_len()) - 2) {
let mut newchunk = self.oaep_encrypt(g, oaep, chunk)?;
res.append(&mut newchunk)
}
Ok(res)
}
fn oaep_encrypt<G: Rng,H:Clone + Input + FixedOutput>(&self, g: &mut G, oaep: &OAEPParams<H>, msg: &[u8])
-> Result<Vec<u8>,RSAError>
{
// Step 1b
if msg.len() > (self.byte_len - (2 * oaep.hash_len()) - 2) {
return Err(RSAError::BadMessageSize)
}
// Step 2a
let mut lhash = oaep.hash(oaep.label.as_bytes());
// Step 2b
let num0s = self.byte_len - msg.len() - (2 * oaep.hash_len()) - 2;
let mut ps = Vec::new();
ps.resize(num0s, 0);
// Step 2c
let mut db = Vec::new();
db.append(&mut lhash);
db.append(&mut ps);
db.push(1);
db.extend_from_slice(msg);
// Step 2d
let seed : Vec<u8> = g.gen_iter().take(oaep.hash_len()).collect();
// Step 2e
let db_mask = oaep.mgf1(&seed, self.byte_len - oaep.hash_len() - 1);
// Step 2f
let mut masked_db = xor_vecs(&db, &db_mask);
// Step 2g
let seed_mask = oaep.mgf1(&masked_db, oaep.hash_len());
// Step 2h
let mut masked_seed = xor_vecs(&seed, &seed_mask);
// Step 2i
let mut em = Vec::new();
em.push(0);
em.append(&mut masked_seed);
em.append(&mut masked_db);
// Step 3a
let m_i = UCN::from_bytes(&em);
// Step 3b
let c_i = ep(&self.nu, &self.e, &m_i);
// Step 3c
let c = c_i.to_bytes(self.byte_len);
Ok(c)
}
}