Encryption! With test cases.
This commit is contained in:
@@ -14,6 +14,7 @@ num = "^0.1.42"
|
||||
rand = "^0.3"
|
||||
sha-1 = "^0.7.0"
|
||||
sha2 = "^0.7.0"
|
||||
simple_asn1 = "^0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "^0.4.1"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
47
src/rsa/oaep.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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..]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import Control.Concurrent
|
||||
import Crypto.Random.DRBG
|
||||
import Data.Bits
|
||||
import qualified Data.ByteString as BS
|
||||
import qualified Data.ByteString.Char8 as BSC
|
||||
import qualified Data.ByteString.Lazy as BSL
|
||||
import Data.Char
|
||||
import Data.List
|
||||
import qualified Data.Map.Strict as Map
|
||||
import GHC.Integer.GMP.Internals
|
||||
@@ -39,6 +41,27 @@ randomByteString g =
|
||||
Right (res, g'') = genBytes (x `mod` 1024) 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 g =
|
||||
randomElement g [(hashSHA1, "1"),
|
||||
@@ -106,6 +129,37 @@ runSignatureGenerator inputs outputs =
|
||||
("s", showBinary (BSL.toStrict sig))]
|
||||
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 outputChan progressBar hndl = go 0
|
||||
where
|
||||
@@ -122,15 +176,21 @@ main =
|
||||
do sizeChan <- newChan
|
||||
outputChan <- newChan
|
||||
--
|
||||
replicateM_ numThreads $
|
||||
void $ forkIO $ runSignatureGenerator sizeChan outputChan
|
||||
--
|
||||
unless (all (`elem` keySizes) keyIterations) $
|
||||
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)
|
||||
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 ()
|
||||
|
||||
randomElement :: CryptoRandomGen g => g -> [a] -> (a, g)
|
||||
|
||||
8598
tests/rsa/encryption.test
Normal file
8598
tests/rsa/encryption.test
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user