Start building out ECC infrastructure.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -20,8 +20,8 @@ Cargo.lock
|
|||||||
**/*.hi
|
**/*.hi
|
||||||
**/*.o
|
**/*.o
|
||||||
**/gen
|
**/gen
|
||||||
tests/dsa/*.class
|
**/*.class
|
||||||
tests/dsa/*.jar
|
**/*.jar
|
||||||
|
|
||||||
# And I started playing with IDEs, so ...
|
# And I started playing with IDEs, so ...
|
||||||
.vscode
|
.vscode
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ license-file = "LICENSE"
|
|||||||
repository = "https://github.com/acw/simple_crypto"
|
repository = "https://github.com/acw/simple_crypto"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
base64 = "^0.9.1"
|
||||||
digest = "^0.7.1"
|
digest = "^0.7.1"
|
||||||
hmac = "^0.5.0"
|
hmac = "^0.5.0"
|
||||||
num = "^0.1.42"
|
num = "^0.1.42"
|
||||||
|
|||||||
86
src/ecdsa/gold_tests.rs
Normal file
86
src/ecdsa/gold_tests.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
use cryptonum::{SCN,UCN};
|
||||||
|
use dsa::rfc6979::*;
|
||||||
|
use ecdsa::curves::EllipticCurve;
|
||||||
|
use ecdsa::math::ECCPoint;
|
||||||
|
use ecdsa::private::ECDSAPrivate;
|
||||||
|
use ecdsa::public::ECDSAPublic;
|
||||||
|
use sha1::Sha1;
|
||||||
|
use sha2::{Sha224,Sha256,Sha384,Sha512};
|
||||||
|
use testing::run_test;
|
||||||
|
|
||||||
|
fn get_curve(cbytes: &[u8]) -> EllipticCurve {
|
||||||
|
match usize::from(UCN::from_bytes(cbytes)) {
|
||||||
|
0x192 => EllipticCurve::p192(),
|
||||||
|
0x224 => EllipticCurve::p224(),
|
||||||
|
0x256 => EllipticCurve::p256(),
|
||||||
|
0x384 => EllipticCurve::p384(),
|
||||||
|
0x521 => EllipticCurve::p521(),
|
||||||
|
_ => panic!("Unacceptable curve identifier")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verification_tests()
|
||||||
|
{
|
||||||
|
run_test("tests/ecdsa/signature.test", 8, |case| {
|
||||||
|
let (neg0, cbytes) = case.get("c").unwrap();
|
||||||
|
let (negx, xbytes) = case.get("x").unwrap();
|
||||||
|
let (negy, ybytes) = case.get("y").unwrap();
|
||||||
|
let (neg1, hbytes) = case.get("h").unwrap();
|
||||||
|
let (neg2, msg) = case.get("m").unwrap();
|
||||||
|
let (neg3, rbytes) = case.get("r").unwrap();
|
||||||
|
let (neg4, sbytes) = case.get("s").unwrap();
|
||||||
|
|
||||||
|
assert!(!neg0 & !neg1 & !neg2 & !neg3 & !neg4);
|
||||||
|
let curve = get_curve(cbytes);
|
||||||
|
let ux = UCN::from_bytes(xbytes);
|
||||||
|
let uy = UCN::from_bytes(ybytes);
|
||||||
|
let x = SCN{ negative: *negx, value: ux };
|
||||||
|
let y = SCN{ negative: *negy, value: uy };
|
||||||
|
let point = ECCPoint::new(&curve, x, y);
|
||||||
|
let public = ECDSAPublic::new(&curve, &point);
|
||||||
|
let r = UCN::from_bytes(rbytes);
|
||||||
|
let s = UCN::from_bytes(sbytes);
|
||||||
|
println!("r: {:X}", r);
|
||||||
|
let sig = DSASignature{ r: r, s: s };
|
||||||
|
|
||||||
|
match usize::from(UCN::from_bytes(hbytes)) {
|
||||||
|
0x1 => assert!(public.verify::<Sha1>(msg, &sig)),
|
||||||
|
0x224 => assert!(public.verify::<Sha224>(msg, &sig)),
|
||||||
|
0x256 => assert!(public.verify::<Sha256>(msg, &sig)),
|
||||||
|
0x384 => assert!(public.verify::<Sha384>(msg, &sig)),
|
||||||
|
0x512 => assert!(public.verify::<Sha512>(msg, &sig)),
|
||||||
|
v => panic!("Bad hash size {}!", v)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signing_tests()
|
||||||
|
{
|
||||||
|
run_test("tests/ecdsa/signature.test", 8, |case| {
|
||||||
|
let (neg0, cbytes) = case.get("c").unwrap();
|
||||||
|
let (neg1, dbytes) = case.get("d").unwrap();
|
||||||
|
let (neg2, hbytes) = case.get("h").unwrap();
|
||||||
|
let (neg3, msg) = case.get("m").unwrap();
|
||||||
|
let (neg4, rbytes) = case.get("r").unwrap();
|
||||||
|
let (neg5, sbytes) = case.get("s").unwrap();
|
||||||
|
|
||||||
|
assert!(!neg0 & !neg1 & !neg2 & !neg3 & !neg4 & !neg5);
|
||||||
|
let curve = get_curve(cbytes);
|
||||||
|
let d = UCN::from_bytes(dbytes);
|
||||||
|
let private = ECDSAPrivate::new(&curve, &d);
|
||||||
|
let r = UCN::from_bytes(rbytes);
|
||||||
|
let s = UCN::from_bytes(sbytes);
|
||||||
|
let sig = DSASignature{ r: r, s: s };
|
||||||
|
|
||||||
|
match usize::from(UCN::from_bytes(hbytes)) {
|
||||||
|
0x1 => assert_eq!(sig, private.sign::<Sha1>(msg)),
|
||||||
|
0x224 => assert_eq!(sig, private.sign::<Sha224>(msg)),
|
||||||
|
0x256 => assert_eq!(sig, private.sign::<Sha256>(msg)),
|
||||||
|
0x384 => assert_eq!(sig, private.sign::<Sha384>(msg)),
|
||||||
|
0x512 => assert_eq!(sig, private.sign::<Sha512>(msg)),
|
||||||
|
v => panic!("Bad hash size {}!", v)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -9,7 +9,19 @@ pub struct ECCPoint {
|
|||||||
pub y: SCN
|
pub y: SCN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn point_add_two_muls(n1: &UCN, p1: &ECCPoint, n2: &UCN, p2: &ECCPoint)
|
||||||
|
-> ECCPoint
|
||||||
|
{
|
||||||
|
let scaled1 = p1.scale(&n1);
|
||||||
|
let scaled2 = p2.scale(&n2);
|
||||||
|
scaled1.add(&scaled2)
|
||||||
|
}
|
||||||
|
|
||||||
impl ECCPoint {
|
impl ECCPoint {
|
||||||
|
pub fn new(ec: &EllipticCurve, x: SCN, y: SCN) -> ECCPoint {
|
||||||
|
ECCPoint { curve: ec.clone(), x: x, y: y }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn default(ec: &EllipticCurve) -> ECCPoint {
|
pub fn default(ec: &EllipticCurve) -> ECCPoint {
|
||||||
ECCPoint {
|
ECCPoint {
|
||||||
curve: ec.clone(),
|
curve: ec.clone(),
|
||||||
@@ -18,6 +30,14 @@ impl ECCPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn negate(&self) -> ECCPoint {
|
||||||
|
ECCPoint {
|
||||||
|
curve: self.curve.clone(),
|
||||||
|
x: self.x.clone(),
|
||||||
|
y: SCN::from(self.curve.p.clone()) - self.y.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn double(&self) -> ECCPoint {
|
pub fn double(&self) -> ECCPoint {
|
||||||
let ua = SCN::from(self.curve.a.clone());
|
let ua = SCN::from(self.curve.a.clone());
|
||||||
let up = SCN::from(self.curve.p.clone());
|
let up = SCN::from(self.curve.p.clone());
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
mod curves;
|
mod curves;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod gold_tests;
|
||||||
mod math;
|
mod math;
|
||||||
mod private;
|
mod private;
|
||||||
mod public;
|
mod public;
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
use digest::{BlockInput,FixedOutput,Input};
|
||||||
|
use digest::generic_array::ArrayLength;
|
||||||
|
use dsa::rfc6979::DSASignature;
|
||||||
use ecdsa::curves::EllipticCurve;
|
use ecdsa::curves::EllipticCurve;
|
||||||
use ecdsa::math::ECCPoint;
|
use ecdsa::math::{ECCPoint,bits2int,point_add_two_muls};
|
||||||
|
use hmac::Hmac;
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone,Debug,PartialEq)]
|
#[derive(Clone,Debug,PartialEq)]
|
||||||
@@ -8,4 +12,48 @@ pub struct ECDSAPublic {
|
|||||||
pub(crate) Q: ECCPoint
|
pub(crate) Q: ECCPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ECDSAPublic {
|
||||||
|
pub fn new(curve: &EllipticCurve, point: &ECCPoint) -> ECDSAPublic {
|
||||||
|
ECDSAPublic {
|
||||||
|
curve: curve.clone(),
|
||||||
|
Q: point.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify<Hash>(&self, m: &[u8], sig: &DSASignature) -> bool
|
||||||
|
where
|
||||||
|
Hash: Clone + BlockInput + Input + FixedOutput + Default,
|
||||||
|
Hmac<Hash>: Clone,
|
||||||
|
Hash::BlockSize: ArrayLength<u8>
|
||||||
|
{
|
||||||
|
let n = &self.curve.n;
|
||||||
|
|
||||||
|
if &sig.r > n {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if &sig.s > n {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = sig.s.modinv(&n);
|
||||||
|
|
||||||
|
let mut digest = <Hash>::default();
|
||||||
|
digest.process(m);
|
||||||
|
let h1: Vec<u8> = digest.fixed_result()
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.map(|x| *x)
|
||||||
|
.collect();
|
||||||
|
let h0 = bits2int(&h1, self.curve.p.bits()) % n;
|
||||||
|
let u1 = (&h0 * &c) % n;
|
||||||
|
let u2 = (&sig.r * &c) % n;
|
||||||
|
let x = point_add_two_muls(&u1, &ECCPoint::default(&self.curve),
|
||||||
|
&u2, &self.Q);
|
||||||
|
|
||||||
|
if x.x.is_negative() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
(x.x.value % n) == sig.r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
214
tests/ecdsa/Generator.java
Normal file
214
tests/ecdsa/Generator.java
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
// Just because I always forget Java compilation:
|
||||||
|
// javac Generator.java -cp bcprov-ext-jdk15on-159.jar
|
||||||
|
// java -cp "bcprov-ext-jdk15on-159.jar:." Generator
|
||||||
|
// Also, go here:
|
||||||
|
// https://www.bouncycastle.org/latest_releases.html
|
||||||
|
//
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.InterruptedException;
|
||||||
|
import java.lang.Math;
|
||||||
|
import java.lang.Thread;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||||
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||||
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.bouncycastle.crypto.Digest;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA224Digest;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA384Digest;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||||
|
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||||
|
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||||
|
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
|
||||||
|
|
||||||
|
class Generator {
|
||||||
|
private FileWriter out;
|
||||||
|
private SecureRandom rng;
|
||||||
|
|
||||||
|
final static int NUM_THREADS = 4;
|
||||||
|
|
||||||
|
public Generator(SecureRandom r, FileWriter o) {
|
||||||
|
rng = r;
|
||||||
|
out = o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runTests(String curveName, int count)
|
||||||
|
throws IOException, InterruptedException
|
||||||
|
{
|
||||||
|
Thread threads[] = new Thread[NUM_THREADS];
|
||||||
|
|
||||||
|
System.out.print("Generating " + curveName + " tests ");
|
||||||
|
for(int i = 0; i < NUM_THREADS; i++) {
|
||||||
|
X9ECParameters x9ECParameters = NISTNamedCurves.getByName(curveName);
|
||||||
|
ECDomainParameters params = new ECDomainParameters(x9ECParameters.getCurve(),
|
||||||
|
x9ECParameters.getG(),
|
||||||
|
x9ECParameters.getN());
|
||||||
|
Runner runner = new Runner(params, count / NUM_THREADS, this);
|
||||||
|
Thread runThread = new Thread(runner);
|
||||||
|
runThread.start();
|
||||||
|
threads[i] = runThread;
|
||||||
|
}
|
||||||
|
for(Thread thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
System.out.println(" done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void output(AsymmetricCipherKeyPair kp,
|
||||||
|
int digestsize,
|
||||||
|
byte[] message,
|
||||||
|
BigInteger[] rs)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
ECPublicKeyParameters pub = (ECPublicKeyParameters)kp.getPublic();
|
||||||
|
ECPrivateKeyParameters priv = (ECPrivateKeyParameters)kp.getPrivate();
|
||||||
|
out.write("c: " + pub.getParameters().getCurve().getFieldSize() + "\n");
|
||||||
|
out.write("x: " + pub.getQ().getAffineXCoord().toBigInteger().toString(16) + "\n");
|
||||||
|
out.write("y: " + pub.getQ().getAffineYCoord().toBigInteger().toString(16) + "\n");
|
||||||
|
out.write("d: " + priv.getD().toString(16) + "\n");
|
||||||
|
out.write("h: " + digestsize + "\n");
|
||||||
|
out.write("m: " + asHex(message) + "\n");
|
||||||
|
out.write("r: " + rs[0].toString(16) + "\n");
|
||||||
|
out.write("s: " + rs[1].toString(16) + "\n");
|
||||||
|
out.flush();
|
||||||
|
System.out.print(".");
|
||||||
|
System.out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Digest appropriateDigest(int nsize)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
switch(nsize) {
|
||||||
|
case 1: return new SHA1Digest();
|
||||||
|
case 160: return new SHA1Digest();
|
||||||
|
case 224: return new SHA224Digest();
|
||||||
|
case 256: return new SHA256Digest();
|
||||||
|
case 384: return new SHA384Digest();
|
||||||
|
case 512: return new SHA512Digest();
|
||||||
|
default:
|
||||||
|
throw new IOException("Bad digest size!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int randomDigestSize()
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
switch(getRandomChoice(5)) {
|
||||||
|
case 0: return 1;
|
||||||
|
case 1: return 224;
|
||||||
|
case 2: return 256;
|
||||||
|
case 3: return 384;
|
||||||
|
case 4: return 512;
|
||||||
|
default:
|
||||||
|
throw new IOException("The world broke.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getRandomChoice(int modulus) {
|
||||||
|
byte randoms[] = new byte[2];
|
||||||
|
rng.nextBytes(randoms);
|
||||||
|
int random = ((int)randoms[0] << 8) + ((int)randoms[1]);
|
||||||
|
return (Math.abs(random) % modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String asHex(byte[] data) {
|
||||||
|
String result = "";
|
||||||
|
|
||||||
|
for(byte value : data) {
|
||||||
|
result = result + String.format("%02x", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
throws IOException, InterruptedException
|
||||||
|
{
|
||||||
|
SecureRandom rng = new SecureRandom();
|
||||||
|
FileWriter outfile = new FileWriter("signature.test", false);
|
||||||
|
Generator gen = new Generator(rng, outfile);
|
||||||
|
|
||||||
|
gen.runTests("P-192", 500);
|
||||||
|
gen.runTests("P-224", 500);
|
||||||
|
gen.runTests("P-256", 500);
|
||||||
|
gen.runTests("P-384", 500);
|
||||||
|
gen.runTests("P-521", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Runner implements Runnable {
|
||||||
|
private ECDomainParameters params;
|
||||||
|
private int count;
|
||||||
|
private Generator parent;
|
||||||
|
|
||||||
|
public Runner(ECDomainParameters params, int count, Generator parent)
|
||||||
|
{
|
||||||
|
this.params = params;
|
||||||
|
this.count = count;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
for(int i = 0; i < count; i++) {
|
||||||
|
runTest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AsymmetricCipherKeyPair getKeyPair()
|
||||||
|
{
|
||||||
|
ECKeyGenerationParameters params =
|
||||||
|
new ECKeyGenerationParameters(this.params, this.parent.rng);
|
||||||
|
ECKeyPairGenerator keygen = new ECKeyPairGenerator();
|
||||||
|
keygen.init(params);
|
||||||
|
return keygen.generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getMessage()
|
||||||
|
{
|
||||||
|
int msgsize = getRandomChoice(1024);
|
||||||
|
byte message[] = new byte[msgsize];
|
||||||
|
rng.nextBytes(message);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] runHash(byte[] msg, int digestsize)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
Digest digestfn = appropriateDigest(digestsize);
|
||||||
|
digestfn.update(msg, 0, msg.length);
|
||||||
|
byte result[] = new byte[digestfn.getDigestSize()];
|
||||||
|
digestfn.doFinal(result, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runTest()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
AsymmetricCipherKeyPair kp = getKeyPair();
|
||||||
|
ECPublicKeyParameters pub = (ECPublicKeyParameters)kp.getPublic();
|
||||||
|
ECPrivateKeyParameters priv = (ECPrivateKeyParameters)kp.getPrivate();
|
||||||
|
|
||||||
|
byte message[] = getMessage();
|
||||||
|
int digestsize = randomDigestSize();
|
||||||
|
byte hash[] = runHash(message, digestsize);
|
||||||
|
|
||||||
|
Digest msgdigest = appropriateDigest(digestsize);
|
||||||
|
HMacDSAKCalculator kgen = new HMacDSAKCalculator(msgdigest);
|
||||||
|
ECDSASigner signer = new ECDSASigner(kgen);
|
||||||
|
signer.init(true, priv);
|
||||||
|
BigInteger rs[] = signer.generateSignature(hash);
|
||||||
|
parent.output(kp, digestsize, message, rs);
|
||||||
|
} catch(IOException exc) {
|
||||||
|
System.out.println("EXCEPTION!");
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20000
tests/ecdsa/signature.test
Normal file
20000
tests/ecdsa/signature.test
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user