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

@@ -14,6 +14,7 @@ num = "^0.1.42"
rand = "^0.3" rand = "^0.3"
sha-1 = "^0.7.0" sha-1 = "^0.7.0"
sha2 = "^0.7.0" sha2 = "^0.7.0"
simple_asn1 = "^0.1.0"
[dev-dependencies] [dev-dependencies]
quickcheck = "^0.4.1" quickcheck = "^0.4.1"

View File

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

View File

@@ -132,6 +132,9 @@ pub fn pkcs1_pad(ident: &[u8], hash: &[u8], keylen: usize) -> Vec<u8> {
result 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)] #[cfg(test)]
mod tests { mod tests {

View File

@@ -1,3 +1,4 @@
use simple_asn1::ASN1DecodeErr;
use std::io; use std::io;
#[derive(Debug)] #[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::*;
use rsa::oaep::OAEPParams;
use sha1::Sha1;
use sha2::{Sha224,Sha256,Sha384,Sha512};
use testing::run_test; use testing::run_test;
fn get_signing_hash(s: usize) -> &'static SigningHash { fn get_signing_hash(s: usize) -> &'static SigningHash {
@@ -56,3 +59,75 @@ fn rsa_verification_tests()
assert!(key.verify(hash, &msg, sig)); 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; mod errors;
#[cfg(test)] #[cfg(test)]
mod gold_tests; mod gold_tests;
mod oaep;
mod public; mod public;
mod private; mod private;
mod signing_hashes; 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 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 rsa::signing_hashes::SigningHash;
use std::fmt; use std::fmt;
@@ -71,4 +74,76 @@ impl RSAPrivate {
let sig = s.to_bytes(self.byte_len); let sig = s.to_bytes(self.byte_len);
sig 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 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; use rsa::signing_hashes::SigningHash;
#[derive(Clone,Debug,PartialEq,Eq)] #[derive(Clone,Debug,PartialEq,Eq)]
@@ -39,4 +43,82 @@ impl RSAPublic {
let em_ = pkcs1_pad(&shash.ident, &hash, self.byte_len); let em_ = pkcs1_pad(&shash.ident, &hash, self.byte_len);
(em == em_) (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)
}
} }

View File

@@ -5,7 +5,9 @@ import Control.Concurrent
import Crypto.Random.DRBG import Crypto.Random.DRBG
import Data.Bits import Data.Bits
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Lazy as BSL
import Data.Char
import Data.List import Data.List
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
import GHC.Integer.GMP.Internals import GHC.Integer.GMP.Internals
@@ -39,6 +41,27 @@ randomByteString g =
Right (res, g'') = genBytes (x `mod` 1024) g' Right (res, g'') = genBytes (x `mod` 1024) g'
in (res, g'') in (res, g'')
randomLabel :: CryptoRandomGen g => g -> (BS.ByteString, g)
randomLabel g =
let Right (ls, g') = genBytes 1 g
[l8] = BS.unpack ls
(letters, g'') = go g' (l8 `mod` 24)
in (BSC.pack letters, g'')
where
goodChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ++
"abcdefghijklmnopqrstuvwxyz" ++
"0123456789 .,/?'\";:[{]}\\|-_=+" ++
"`~!@#$%^&*()"
lenGoods = fromIntegral (length goodChars)
--
go g 0 = ("", g)
go g x =
let Right (bs, g') = genBytes 1 g
[x] = BS.unpack bs
idx = fromIntegral (x `mod` lenGoods)
(rest, g'') = go g' (x - 1)
in ((goodChars !! idx) : rest, g'')
randomHash :: CryptoRandomGen g => g -> ((HashInfo, String), g) randomHash :: CryptoRandomGen g => g -> ((HashInfo, String), g)
randomHash g = randomHash g =
randomElement g [(hashSHA1, "1"), randomElement g [(hashSHA1, "1"),
@@ -106,6 +129,37 @@ runSignatureGenerator inputs outputs =
("s", showBinary (BSL.toStrict sig))] ("s", showBinary (BSL.toStrict sig))]
go Nothing g3 go Nothing g3
runEncryptionGenerator :: Chan Int -> Chan [(String,String)] -> IO ()
runEncryptionGenerator inputs outputs =
do rng0 :: GenBuffered SystemRandom <- newGenIO
go Nothing rng0
where
go Nothing rng0 =
do keySize <- readChan inputs
go (Just keySize) rng0
go (Just keySize) g0 =
do unless (keySize `elem` keySizes) $
fail ("Bad key size: " ++ show keySize)
let Right (public, private, g1) = generateKeyPair g0 keySize
let (message, g2) = randomByteString g1
let (label, g3) = randomLabel g2
let ((hashinfo, hashname), g4) = randomHash g3
let hash = hashFunction hashinfo
let mgf1 = generateMGF1 hash
let msg = BSL.fromStrict message
lbl = BSL.fromStrict label
case encryptOAEP g4 hash mgf1 lbl public msg of
Left _ ->
go (Just keySize) g4
Right (c, g5) ->
do writeChan outputs [("d", showHex (private_d private) ""),
("n", showHex (public_n public) ""),
("h", hashname),
("l", showBinary label),
("m", showBinary message),
("c", showBinary (BSL.toStrict c))]
go Nothing g5
writeData :: Chan [(String,String)] -> (Progress -> IO ()) -> Handle -> IO () writeData :: Chan [(String,String)] -> (Progress -> IO ()) -> Handle -> IO ()
writeData outputChan progressBar hndl = go 0 writeData outputChan progressBar hndl = go 0
where where
@@ -122,15 +176,21 @@ main =
do sizeChan <- newChan do sizeChan <- newChan
outputChan <- newChan outputChan <- newChan
-- --
replicateM_ numThreads $
void $ forkIO $ runSignatureGenerator sizeChan outputChan
--
unless (all (`elem` keySizes) keyIterations) $ unless (all (`elem` keySizes) keyIterations) $
fail "System setup failure." fail "System setup failure."
writeList2Chan sizeChan keyIterations
-- --
let bar = autoProgressBar (msg "Generating test material") percentage 60 sigthrs <- replicateM numThreads $
forkIO $ runSignatureGenerator sizeChan outputChan
let bar = autoProgressBar (msg "Generating signature tests") percentage 60
writeList2Chan sizeChan keyIterations
g1 <- withFile "signature.test" WriteMode (writeData outputChan bar) g1 <- withFile "signature.test" WriteMode (writeData outputChan bar)
mapM_ killThread sigthrs
--
replicateM_ numThreads $
void $ forkIO $ runEncryptionGenerator sizeChan outputChan
let bar = autoProgressBar (msg "Generating encryption tests") percentage 60
writeList2Chan sizeChan keyIterations
g2 <- withFile "encryption.test" WriteMode (writeData outputChan bar)
return () return ()
randomElement :: CryptoRandomGen g => g -> [a] -> (a, g) randomElement :: CryptoRandomGen g => g -> [a] -> (a, g)

8598
tests/rsa/encryption.test Normal file

File diff suppressed because one or more lines are too long