Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion parity-crypto/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "parity-crypto"
version = "0.4.1"
version = "0.5.0"
Comment thread
ordian marked this conversation as resolved.
Outdated
authors = ["Parity Technologies <admin@parity.io>"]
repository = "https://github.com/paritytech/parity-common"
description = "Crypto utils used by ethstore and network."
Expand All @@ -15,6 +15,9 @@ harness = false

[dependencies]
tiny-keccak = "1.4"
eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1" }
Comment thread
grbIzl marked this conversation as resolved.
Outdated
ethereum-types = "0.6.0"
Comment thread
ordian marked this conversation as resolved.
Outdated
lazy_static = "1.0"
scrypt = { version = "0.2", default-features = false }
ripemd160 = "0.8.0"
sha2 = "0.8.0"
Expand All @@ -24,6 +27,9 @@ aes = "0.3.2"
aes-ctr = "0.3.0"
block-modes = "0.3.3"
pbkdf2 = "0.3.0"
quick-error = "1.2.2"
Comment thread
grbIzl marked this conversation as resolved.
Outdated
rand = "0.6"
Comment thread
dvdplm marked this conversation as resolved.
rustc-hex = "1.0"
Comment thread
grbIzl marked this conversation as resolved.
Outdated
subtle = "2.1"
zeroize = "0.9.1"

Expand Down
189 changes: 189 additions & 0 deletions parity-crypto/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.

// Parity Ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity Ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.

Comment thread
grbIzl marked this conversation as resolved.
Outdated
use secp256k1;
use std::io;
use crate::error::SymmError;
use quick_error::quick_error;

quick_error! {
Comment thread
grbIzl marked this conversation as resolved.
Outdated
#[derive(Debug)]
pub enum Error {
Secp(e: secp256k1::Error) {
display("secp256k1 error: {}", e)
cause(e)
from()
}
Io(e: io::Error) {
display("i/o error: {}", e)
cause(e)
from()
}
InvalidMessage {
display("invalid message")
}
Symm(e: SymmError) {
cause(e)
from()
}
}
}

/// ECDH functions
pub mod ecdh {
use secp256k1::{self, ecdh, key};
use super::Error;
use crate::{Secret, Public, SECP256K1};

/// Agree on a shared secret
pub fn agree(secret: &Secret, public: &Public) -> Result<Secret, Error> {
let context = &SECP256K1;
let pdata = {
let mut temp = [4u8; 65];
(&mut temp[1..65]).copy_from_slice(&public[0..64]);
temp
};

let publ = key::PublicKey::from_slice(context, &pdata)?;
let sec = key::SecretKey::from_slice(context, secret.as_bytes())?;
let shared = ecdh::SharedSecret::new_raw(context, &publ, &sec);

Secret::from_unsafe_slice(&shared[0..32])
.map_err(|_| Error::Secp(secp256k1::Error::InvalidSecretKey))
}
}

/// ECIES function
pub mod ecies {
use ethereum_types::H128;
use super::{ecdh, Error};
use crate::{Random, Generator, Public, Secret, aes, digest, hmac, is_equal};

/// Encrypt a message with a public key, writing an HMAC covering both
/// the plaintext and authenticated data.
///
/// Authenticated data may be empty.
pub fn encrypt(public: &Public, auth_data: &[u8], plain: &[u8]) -> Result<Vec<u8>, Error> {
let r = Random.generate()?;
let z = ecdh::agree(r.secret(), public)?;
let mut key = [0u8; 32];
kdf(&z, &[0u8; 0], &mut key);

let ekey = &key[0..16];
let mkey = hmac::SigKey::sha256(&digest::sha256(&key[16..32]));

let mut msg = vec![0u8; 1 + 64 + 16 + plain.len() + 32];
msg[0] = 0x04u8;
Comment thread
grbIzl marked this conversation as resolved.
Outdated
{
let msgd = &mut msg[1..];
Comment thread
grbIzl marked this conversation as resolved.
Outdated
msgd[0..64].copy_from_slice(r.public().as_bytes());
let iv = H128::random();
Comment thread
ordian marked this conversation as resolved.
Outdated
msgd[64..80].copy_from_slice(iv.as_bytes());
{
let cipher = &mut msgd[(64 + 16)..(64 + 16 + plain.len())];
aes::encrypt_128_ctr(ekey, iv.as_bytes(), plain, cipher)?;
}
let mut hmac = hmac::Signer::with(&mkey);
{
let cipher_iv = &msgd[64..(64 + 16 + plain.len())];
hmac.update(cipher_iv);
}
hmac.update(auth_data);
let sig = hmac.sign();
msgd[(64 + 16 + plain.len())..].copy_from_slice(&sig);
}
Ok(msg)
}

/// Decrypt a message with a secret key, checking HMAC for ciphertext
/// and authenticated data validity.
pub fn decrypt(secret: &Secret, auth_data: &[u8], encrypted: &[u8]) -> Result<Vec<u8>, Error> {
let meta_len = 1 + 64 + 16 + 32;
Comment thread
grbIzl marked this conversation as resolved.
Outdated
if encrypted.len() < meta_len || encrypted[0] < 2 || encrypted[0] > 4 {
return Err(Error::InvalidMessage); //invalid message: publickey
}

let e = &encrypted[1..];
let p = Public::from_slice(&e[0..64]);
let z = ecdh::agree(secret, &p)?;
let mut key = [0u8; 32];
kdf(&z, &[0u8; 0], &mut key);

let ekey = &key[0..16];
let mkey = hmac::SigKey::sha256(&digest::sha256(&key[16..32]));

let clen = encrypted.len() - meta_len;
Comment thread
grbIzl marked this conversation as resolved.
Outdated
let cipher_with_iv = &e[64..(64+16+clen)];
let cipher_iv = &cipher_with_iv[0..16];
let cipher_no_iv = &cipher_with_iv[16..];
let msg_mac = &e[(64+16+clen)..];

// Verify tag
let mut hmac = hmac::Signer::with(&mkey);
hmac.update(cipher_with_iv);
hmac.update(auth_data);
let mac = hmac.sign();

if !is_equal(&mac.as_ref()[..], msg_mac) {
return Err(Error::InvalidMessage);
}

let mut msg = vec![0u8; clen];
aes::decrypt_128_ctr(ekey, cipher_iv, cipher_no_iv, &mut msg[..])?;
Ok(msg)
}

fn kdf(secret: &Secret, s1: &[u8], dest: &mut [u8]) {
// SEC/ISO/Shoup specify counter size SHOULD be equivalent
// to size of hash output, however, it also notes that
// the 4 bytes is okay. NIST specifies 4 bytes.
let mut ctr = 1u32;
let mut written = 0usize;
while written < dest.len() {
let mut hasher = digest::Hasher::sha256();
let ctrs = [(ctr >> 24) as u8, (ctr >> 16) as u8, (ctr >> 8) as u8, ctr as u8];
hasher.update(&ctrs);
hasher.update(secret.as_bytes());
hasher.update(s1);
let d = hasher.finish();
&mut dest[written..(written + 32)].copy_from_slice(&d);
written += 32;
ctr += 1;
}
}
}

#[cfg(test)]
mod tests {
use super::ecies;
use crate::{Random, Generator};

#[test]
fn ecies_shared() {
let kp = Random.generate().unwrap();
let message = b"So many books, so little time";

let shared = b"shared";
let wrong_shared = b"incorrect";
let encrypted = ecies::encrypt(kp.public(), shared, message).unwrap();
assert!(encrypted[..] != message[..]);
assert_eq!(encrypted[0], 0x04);

assert!(ecies::decrypt(kp.secret(), wrong_shared, &encrypted).is_err());
let decrypted = ecies::decrypt(kp.secret(), shared, &encrypted).unwrap();
assert_eq!(decrypted[..message.len()], message[..]);
}
}
46 changes: 46 additions & 0 deletions parity-crypto/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ use std::{fmt, result, error::Error as StdError};
pub enum Error {
Scrypt(ScryptError),
Symm(SymmError),
/// Invalid secret key
InvalidSecret,
Comment thread
grbIzl marked this conversation as resolved.
Outdated
/// Invalid public key
InvalidPublic,
Comment thread
grbIzl marked this conversation as resolved.
Outdated
/// Invalid address
InvalidAddress,
/// Invalid EC signature
InvalidSignature,
/// Invalid AES message
InvalidMessage,
/// IO Error
Io(::std::io::Error),
/// Custom
Custom(String),
}

#[derive(Debug)]
Expand Down Expand Up @@ -47,6 +61,8 @@ impl StdError for Error {
match self {
Error::Scrypt(scrypt_err) => Some(scrypt_err),
Error::Symm(symm_err) => Some(symm_err),
Error::Io(err) => Some(err),
_ => None,
}
}
}
Expand Down Expand Up @@ -76,6 +92,13 @@ impl fmt::Display for Error {
match self {
Error::Scrypt(err)=> write!(f, "scrypt error: {}", err),
Error::Symm(err) => write!(f, "symm error: {}", err),
Error::InvalidSecret => write!(f, "invalid secret"),
Error::InvalidPublic => write!(f, "invalid public"),
Error::InvalidAddress => write!(f, "invalid address"),
Error::InvalidSignature => write!(f, "invalid EC signature"),
Error::InvalidMessage => write!(f, "invalid AES message"),
Error::Io(err) => write!(f, "I/O error: {}", err),
Error::Custom(err) => write!(f, "custom crypto error: {}", err),
}
}
}
Expand All @@ -101,12 +124,35 @@ impl fmt::Display for SymmError {
}
}

impl Into<String> for Error {
fn into(self) -> String {
format!("{}", self)
}
}

impl Into<std::io::Error> for Error {
fn into(self) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::Other, format!("Crypto error: {}",self))
}
}

impl From<::std::io::Error> for Error {
fn from(err: ::std::io::Error) -> Error {
Error::Io(err)
}
}

impl From<::secp256k1::Error> for Error {
fn from(e: ::secp256k1::Error) -> Error {
match e {
::secp256k1::Error::InvalidMessage => Error::InvalidMessage,
::secp256k1::Error::InvalidPublicKey => Error::InvalidPublic,
::secp256k1::Error::InvalidSecretKey => Error::InvalidSecret,
_ => Error::InvalidSignature,
}
}
}

impl From<block_modes::BlockModeError> for SymmError {
fn from(e: block_modes::BlockModeError) -> SymmError {
SymmError(PrivSymmErr::BlockMode(e))
Expand Down
Loading