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
|
||||
|
||||
### CryptoBox easy encryption
|
||||
```crystal
|
||||
require "cox"
|
||||
|
||||
data = "Hello World!"
|
||||
|
||||
# Alice is the sender
|
||||
alice = Cox::KeyPair.new
|
||||
alice = Cox::CryptoBox::SecretKey.new
|
||||
|
||||
# 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
|
||||
# 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
|
||||
# Alice's public key
|
||||
decrypted = Cox.decrypt(encrypted, nonce, alice.public, bob.secret)
|
||||
# Precompute within a block. The shared secret is wiped when the block exits.
|
||||
bob.pair alice.public_key do |pair|
|
||||
# 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
|
||||
|
|
|
@ -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"
|
||||
|
||||
describe Cox do
|
||||
# TODO: Write tests
|
||||
|
||||
it "works for encrypting" do
|
||||
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
|
||||
# Finished in 71 microseconds
|
||||
# 1 examples, 2 failures, 5 errors, 0 pending
|
||||
# What a man.
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ end
|
|||
require "./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_size = data_buffer.bytesize
|
||||
output_buffer = Bytes.new(data_buffer.bytesize + LibSodium::MAC_SIZE)
|
||||
|
@ -23,12 +23,12 @@ module Cox
|
|||
output_buffer
|
||||
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, encrypt(data, nonce, recipient_public_key, sender_secret_key)}
|
||||
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_size = data_buffer.bytesize
|
||||
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
|
||||
property bytes : Bytes
|
||||
|
||||
KEY_SIZE = LibSodium::PUBLIC_KEY_SIZE
|
||||
|
||||
getter bytes : Bytes
|
||||
|
||||
def initialize(@bytes : Bytes)
|
||||
if bytes.bytesize != KEY_SIZE
|
||||
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
|
||||
|
||||
def self.new
|
||||
new(Random::Secure.random_bytes(NONCE_SIZE))
|
||||
def initialize
|
||||
@bytes = Random::Secure.random_bytes(NONCE_SIZE)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,16 +2,18 @@ require "../lib_sodium"
|
|||
|
||||
module Cox
|
||||
class Sign::PublicKey < Key
|
||||
property bytes : Bytes
|
||||
|
||||
KEY_SIZE = LibSodium::PUBLIC_SIGN_SIZE
|
||||
|
||||
getter bytes : Bytes
|
||||
|
||||
def initialize(@bytes : Bytes)
|
||||
if bytes.bytesize != KEY_SIZE
|
||||
raise ArgumentError.new("Public key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
|
||||
end
|
||||
end
|
||||
|
||||
# Verify signature made by `secret_key.sign_detached(message)`
|
||||
# Raises on verification failure.
|
||||
def verify_detached(message, sig : Bytes)
|
||||
verify_detached message.to_slice, sig
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ module Cox
|
|||
LibSodium.crypto_sign_keypair pkey, @bytes
|
||||
end
|
||||
|
||||
# Use existing Private and Public keys.
|
||||
# Use existing Secret and Public keys.
|
||||
def initialize(@bytes : Bytes, pkey : Bytes)
|
||||
raise ArgumentError.new("Secret sign key must be #{KEY_SIZE}, got #{@bytes.bytesize}")
|
||||
@public_key = PublicKey.new pkey
|
||||
|
@ -31,6 +31,8 @@ module Cox
|
|||
# Also needs to differentiate from seed as a single parameter
|
||||
# end
|
||||
|
||||
# Signs message and returns a detached signature.
|
||||
# Verify using `secret_key.public_key.verify_detached(message, sig)`
|
||||
def sign_detached(message)
|
||||
sign_detached message.to_slice
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue