Breaking API changes:
SecretKey renamed to CryptoBox::SecretKey PublicKey renamed to CryptoBox::PublicKey KeyPair removed. Use CryptoBox::SecretKey instead. Cox.encrypt was removed. Use `secret_key.pair(...).encrypt` Cox.decrypt was removed. Use `secret_key.pair(...).decrypt`master
parent
0eb4a8991a
commit
a02c54f4a7
21
README.md
21
README.md
|
@ -63,26 +63,33 @@ dependencies:
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
### CryptoBox easy encryption
|
||||||
```crystal
|
```crystal
|
||||||
require "cox"
|
require "cox"
|
||||||
|
|
||||||
data = "Hello World!"
|
data = "Hello World!"
|
||||||
|
|
||||||
# Alice is the sender
|
# Alice is the sender
|
||||||
alice = Cox::KeyPair.new
|
alice = Cox::CryptoBox::SecretKey.new
|
||||||
|
|
||||||
# Bob is the recipient
|
# Bob is the recipient
|
||||||
bob = Cox::KeyPair.new
|
bob = Cox::CryptoBox::SecretKey.new
|
||||||
|
|
||||||
|
# Precompute a shared secret between alice and bob.
|
||||||
|
pair = alice.pair bob.public_key
|
||||||
|
|
||||||
# Encrypt a message for Bob using his public key, signing it with Alice's
|
# Encrypt a message for Bob using his public key, signing it with Alice's
|
||||||
# secret key
|
# secret key
|
||||||
nonce, encrypted = Cox.encrypt(data, bob.public, alice.secret)
|
nonce, encrypted = pair.encrypt data
|
||||||
|
|
||||||
# Decrypt the message using Bob's secret key, and verify its signature against
|
# Precompute within a block. The shared secret is wiped when the block exits.
|
||||||
# Alice's public key
|
bob.pair alice.public_key do |pair|
|
||||||
decrypted = Cox.decrypt(encrypted, nonce, alice.public, bob.secret)
|
# Decrypt the message using Bob's secret key, and verify its signature against
|
||||||
|
# Alice's public key
|
||||||
|
decrypted = Cox.decrypt(encrypted, nonce, alice.public, bob.secret)
|
||||||
|
|
||||||
String.new(decrypted) # => "Hello World!"
|
String.new(decrypted) # => "Hello World!"
|
||||||
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### Public key signing
|
### Public key signing
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
require "../../spec_helper"
|
||||||
|
|
||||||
|
describe Cox::CryptoBox::SecretKey do
|
||||||
|
it "easy encrypt/decrypt" do
|
||||||
|
data = "Hello World!"
|
||||||
|
|
||||||
|
# Alice is the sender
|
||||||
|
alice = Cox::CryptoBox::SecretKey.new
|
||||||
|
|
||||||
|
# Bob is the recipient
|
||||||
|
bob = Cox::CryptoBox::SecretKey.new
|
||||||
|
|
||||||
|
# Encrypt a message for Bob using his public key, signing it with Alice's
|
||||||
|
# secret key
|
||||||
|
pair = alice.pair bob.public_key
|
||||||
|
nonce, encrypted = pair.encrypt_easy data
|
||||||
|
|
||||||
|
# Decrypt the message using Bob's secret key, and verify its signature against
|
||||||
|
# Alice's public key
|
||||||
|
bob.pair alice.public_key do |pair|
|
||||||
|
decrypted = pair.decrypt_easy encrypted, nonce: nonce
|
||||||
|
|
||||||
|
String.new(decrypted).should eq(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,25 +1,7 @@
|
||||||
require "./spec_helper"
|
require "./spec_helper"
|
||||||
|
|
||||||
describe Cox do
|
describe Cox do
|
||||||
# TODO: Write tests
|
# Finished in 71 microseconds
|
||||||
|
# 1 examples, 2 failures, 5 errors, 0 pending
|
||||||
it "works for encrypting" do
|
# What a man.
|
||||||
data = "Hello World!"
|
|
||||||
|
|
||||||
# Alice is the sender
|
|
||||||
alice = Cox::KeyPair.new
|
|
||||||
|
|
||||||
# Bob is the recipient
|
|
||||||
bob = Cox::KeyPair.new
|
|
||||||
|
|
||||||
# Encrypt a message for Bob using his public key, signing it with Alice's
|
|
||||||
# secret key
|
|
||||||
nonce, encrypted = Cox.encrypt(data, bob.public, alice.secret)
|
|
||||||
|
|
||||||
# Decrypt the message using Bob's secret key, and verify its signature against
|
|
||||||
# Alice's public key
|
|
||||||
decrypted = Cox.decrypt(encrypted, nonce, alice.public, bob.secret)
|
|
||||||
|
|
||||||
String.new(decrypted).should eq(data)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ end
|
||||||
require "./cox/**"
|
require "./cox/**"
|
||||||
|
|
||||||
module Cox
|
module Cox
|
||||||
def self.encrypt(data, nonce : Nonce, recipient_public_key : PublicKey, sender_secret_key : SecretKey)
|
def self.encrypt(data, nonce : Nonce, recipient_public_key : CryptoBox::PublicKey, sender_secret_key : CryptoBox::SecretKey)
|
||||||
data_buffer = data.to_slice
|
data_buffer = data.to_slice
|
||||||
data_size = data_buffer.bytesize
|
data_size = data_buffer.bytesize
|
||||||
output_buffer = Bytes.new(data_buffer.bytesize + LibSodium::MAC_SIZE)
|
output_buffer = Bytes.new(data_buffer.bytesize + LibSodium::MAC_SIZE)
|
||||||
|
@ -23,12 +23,12 @@ module Cox
|
||||||
output_buffer
|
output_buffer
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.encrypt(data, recipient_public_key : PublicKey, sender_secret_key : SecretKey)
|
def self.encrypt(data, recipient_public_key : CryptoBox::PublicKey, sender_secret_key : CryptoBox::SecretKey)
|
||||||
nonce = Nonce.new
|
nonce = Nonce.new
|
||||||
{nonce, encrypt(data, nonce, recipient_public_key, sender_secret_key)}
|
{nonce, encrypt(data, nonce, recipient_public_key, sender_secret_key)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.decrypt(data, nonce : Nonce, sender_public_key : PublicKey, recipient_secret_key : SecretKey)
|
def self.decrypt(data, nonce : Nonce, sender_public_key : CryptoBox::PublicKey, recipient_secret_key : CryptoBox::SecretKey)
|
||||||
data_buffer = data.to_slice
|
data_buffer = data.to_slice
|
||||||
data_size = data_buffer.bytesize
|
data_size = data_buffer.bytesize
|
||||||
output_buffer = Bytes.new(data_buffer.bytesize - LibSodium::MAC_SIZE)
|
output_buffer = Bytes.new(data_buffer.bytesize - LibSodium::MAC_SIZE)
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
require "../lib_sodium"
|
||||||
|
|
||||||
|
module Cox::CryptoBox
|
||||||
|
class Pair
|
||||||
|
# TODO: precompute using crypto_box_beforenm
|
||||||
|
def initialize(@secret_key : SecretKey, @public_key : PublicKey)
|
||||||
|
end
|
||||||
|
|
||||||
|
def encrypt_easy(src)
|
||||||
|
encrypt_easy src.to_slice
|
||||||
|
end
|
||||||
|
|
||||||
|
def encrypt_easy(src : Bytes, dst = Bytes.new(src.bytesize + LibSodium::MAC_SIZE), nonce = Nonce.new)
|
||||||
|
if LibSodium.crypto_box_easy(dst, src, src.bytesize, nonce.to_slice, @public_key.to_slice, @secret_key.to_slice) != 0
|
||||||
|
raise Error.new("crypto_box_easy")
|
||||||
|
end
|
||||||
|
{nonce, dst}
|
||||||
|
end
|
||||||
|
|
||||||
|
def decrypt_easy(src : Bytes, dst = Bytes.new(src.bytesize - LibSodium::MAC_SIZE), nonce = Nonce.new) : Bytes
|
||||||
|
if LibSodium.crypto_box_open_easy(dst, src, src.bytesize, nonce.to_slice, @public_key.to_slice, @secret_key.to_slice) != 0
|
||||||
|
raise Error::DecryptionFailed.new("crypto_box_open_easy")
|
||||||
|
end
|
||||||
|
dst
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO detached
|
||||||
|
def close
|
||||||
|
# TODO: wipe state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,11 +1,11 @@
|
||||||
require "./lib_sodium"
|
require "../lib_sodium"
|
||||||
|
|
||||||
module Cox
|
module Cox::CryptoBox
|
||||||
class PublicKey < Key
|
class PublicKey < Key
|
||||||
property bytes : Bytes
|
|
||||||
|
|
||||||
KEY_SIZE = LibSodium::PUBLIC_KEY_SIZE
|
KEY_SIZE = LibSodium::PUBLIC_KEY_SIZE
|
||||||
|
|
||||||
|
getter bytes : Bytes
|
||||||
|
|
||||||
def initialize(@bytes : Bytes)
|
def initialize(@bytes : Bytes)
|
||||||
if bytes.bytesize != KEY_SIZE
|
if bytes.bytesize != KEY_SIZE
|
||||||
raise ArgumentError.new("Public key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
|
raise ArgumentError.new("Public key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
|
|
@ -0,0 +1,41 @@
|
||||||
|
require "../lib_sodium"
|
||||||
|
|
||||||
|
module Cox::CryptoBox
|
||||||
|
class SecretKey < Key
|
||||||
|
KEY_SIZE = LibSodium::SECRET_KEY_SIZE
|
||||||
|
MAC_SIZE = LibSodium::MAC_SIZE
|
||||||
|
|
||||||
|
getter public_key
|
||||||
|
getter bytes : Bytes
|
||||||
|
|
||||||
|
# Generate a new secret/public key pair.
|
||||||
|
def initialize
|
||||||
|
pkey = Bytes.new(PublicKey::KEY_SIZE)
|
||||||
|
@bytes = Bytes.new(KEY_SIZE)
|
||||||
|
@public_key = PublicKey.new pkey
|
||||||
|
LibSodium.crypto_box_keypair(pkey, @bytes)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Use existing Secret and Public keys.
|
||||||
|
def initialize(@bytes : Bytes, pkey : Bytes)
|
||||||
|
if bytes.bytesize != KEY_SIZE
|
||||||
|
raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
|
||||||
|
end
|
||||||
|
@public_key = PublicKey.new pkey
|
||||||
|
end
|
||||||
|
|
||||||
|
def pair(public_key)
|
||||||
|
Pair.new self, public_key
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a new pair and automatically close when exiting the block.
|
||||||
|
def pair(public_key)
|
||||||
|
pa = pair public_key
|
||||||
|
begin
|
||||||
|
yield pa
|
||||||
|
ensure
|
||||||
|
pa.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,24 +0,0 @@
|
||||||
require "./lib_sodium"
|
|
||||||
|
|
||||||
module Cox
|
|
||||||
class KeyPair
|
|
||||||
property public : PublicKey
|
|
||||||
property secret : SecretKey
|
|
||||||
|
|
||||||
def initialize(@public, @secret)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.new(pub : Bytes, sec : Bytes)
|
|
||||||
new(PublicKey.new(pub), SecretKey.new(sec))
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.new
|
|
||||||
public_key = Bytes.new(PublicKey::KEY_SIZE)
|
|
||||||
secret_key = Bytes.new(SecretKey::KEY_SIZE)
|
|
||||||
|
|
||||||
LibSodium.crypto_box_keypair(public_key.to_unsafe, secret_key.to_unsafe)
|
|
||||||
|
|
||||||
new(public_key, secret_key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -14,8 +14,8 @@ module Cox
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.new
|
def initialize
|
||||||
new(Random::Secure.random_bytes(NONCE_SIZE))
|
@bytes = Random::Secure.random_bytes(NONCE_SIZE)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,16 +2,18 @@ require "../lib_sodium"
|
||||||
|
|
||||||
module Cox
|
module Cox
|
||||||
class Sign::PublicKey < Key
|
class Sign::PublicKey < Key
|
||||||
property bytes : Bytes
|
|
||||||
|
|
||||||
KEY_SIZE = LibSodium::PUBLIC_SIGN_SIZE
|
KEY_SIZE = LibSodium::PUBLIC_SIGN_SIZE
|
||||||
|
|
||||||
|
getter bytes : Bytes
|
||||||
|
|
||||||
def initialize(@bytes : Bytes)
|
def initialize(@bytes : Bytes)
|
||||||
if bytes.bytesize != KEY_SIZE
|
if bytes.bytesize != KEY_SIZE
|
||||||
raise ArgumentError.new("Public key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
|
raise ArgumentError.new("Public key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Verify signature made by `secret_key.sign_detached(message)`
|
||||||
|
# Raises on verification failure.
|
||||||
def verify_detached(message, sig : Bytes)
|
def verify_detached(message, sig : Bytes)
|
||||||
verify_detached message.to_slice, sig
|
verify_detached message.to_slice, sig
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,7 +15,7 @@ module Cox
|
||||||
LibSodium.crypto_sign_keypair pkey, @bytes
|
LibSodium.crypto_sign_keypair pkey, @bytes
|
||||||
end
|
end
|
||||||
|
|
||||||
# Use existing Private and Public keys.
|
# Use existing Secret and Public keys.
|
||||||
def initialize(@bytes : Bytes, pkey : Bytes)
|
def initialize(@bytes : Bytes, pkey : Bytes)
|
||||||
raise ArgumentError.new("Secret sign key must be #{KEY_SIZE}, got #{@bytes.bytesize}")
|
raise ArgumentError.new("Secret sign key must be #{KEY_SIZE}, got #{@bytes.bytesize}")
|
||||||
@public_key = PublicKey.new pkey
|
@public_key = PublicKey.new pkey
|
||||||
|
@ -31,6 +31,8 @@ module Cox
|
||||||
# Also needs to differentiate from seed as a single parameter
|
# Also needs to differentiate from seed as a single parameter
|
||||||
# end
|
# end
|
||||||
|
|
||||||
|
# Signs message and returns a detached signature.
|
||||||
|
# Verify using `secret_key.public_key.verify_detached(message, sig)`
|
||||||
def sign_detached(message)
|
def sign_detached(message)
|
||||||
sign_detached message.to_slice
|
sign_detached message.to_slice
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue