Breaking API changes:

SecretKey renamed to SecretBox.

Start of automatic wiping.
Documentation additions and corrections.
master
Didactic Drunk 2019-06-28 05:20:56 -07:00
parent a02c54f4a7
commit 848cf3e3e2
11 changed files with 60 additions and 43 deletions

View File

@ -40,6 +40,7 @@ Updated Crystal bindings for the [libsodium API](https://libsodium.gitbook.io/do
- [x] ChaCha20 - [x] ChaCha20
- [ ] One time auth - [ ] One time auth
- [ ] Padding - [ ] Padding
- [?] Semi-automatic memory wiping.
☑ Indicate specs are compared against test vectors from another source. ☑ Indicate specs are compared against test vectors from another source.
@ -111,7 +112,7 @@ public_key.verify_detached message, signature
### Secret Key Encryption ### Secret Key Encryption
```crystal ```crystal
key = Cox::SecretKey.random key = Cox::SecretKey.new
message = "foobar" message = "foobar"
encrypted, nonce = key.encrypt_easy message encrypted, nonce = key.encrypt_easy message

View File

@ -1,8 +1,8 @@
require "../spec_helper" require "../spec_helper"
describe Cox::SecretKey do describe Cox::SecretBox do
it "encrypts/decrypts" do it "encrypts/decrypts" do
key = Cox::SecretKey.random key = Cox::SecretBox.new
message = "foobar" message = "foobar"
encrypted, nonce = key.encrypt_easy message encrypted, nonce = key.encrypt_easy message

View File

@ -8,37 +8,14 @@ module Cox
class DecryptionFailed < Error class DecryptionFailed < Error
end end
end end
def self.memzero(bytes : Bytes)
LibSodium.sodium_memzero bytes, bytes.bytesize
end
end end
require "./cox/**" require "./cox/**"
module Cox
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)
if LibSodium.crypto_box_easy(output_buffer.to_slice, data_buffer, data_size, nonce.to_slice, recipient_public_key.to_slice, sender_secret_key.to_slice) != 0
raise Error.new("crypto_box_easy")
end
output_buffer
end
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 : 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)
if LibSodium.crypto_box_open_easy(output_buffer.to_slice, data_buffer.to_slice, data_size, nonce.to_slice, sender_public_key.to_slice, recipient_secret_key.to_slice) != 0
raise Error::DecryptionFailed.new("crypto_box_open_easy")
end
output_buffer
end
end
if Cox::LibSodium.sodium_init == -1 if Cox::LibSodium.sodium_init == -1
abort "Failed to init libsodium" abort "Failed to init libsodium"
end end

View File

@ -2,8 +2,13 @@ require "../lib_sodium"
module Cox::CryptoBox module Cox::CryptoBox
class Pair class Pair
# TODO: precompute using crypto_box_beforenm include Wipe
# BUG: precompute size
@bytes = Bytes.new(1)
def initialize(@secret_key : SecretKey, @public_key : PublicKey) def initialize(@secret_key : SecretKey, @public_key : PublicKey)
# TODO: precompute using crypto_box_beforenm
end end
def encrypt_easy(src) def encrypt_easy(src)
@ -25,8 +30,5 @@ module Cox::CryptoBox
end end
# TODO detached # TODO detached
def close
# TODO: wipe state
end
end end
end end

View File

@ -2,6 +2,7 @@ require "../lib_sodium"
module Cox::CryptoBox module Cox::CryptoBox
class PublicKey < Key class PublicKey < Key
include Wipe
KEY_SIZE = LibSodium::PUBLIC_KEY_SIZE KEY_SIZE = LibSodium::PUBLIC_KEY_SIZE
getter bytes : Bytes getter bytes : Bytes

View File

@ -2,6 +2,7 @@ require "../lib_sodium"
module Cox::CryptoBox module Cox::CryptoBox
class SecretKey < Key class SecretKey < Key
include Wipe
KEY_SIZE = LibSodium::SECRET_KEY_SIZE KEY_SIZE = LibSodium::SECRET_KEY_SIZE
MAC_SIZE = LibSodium::MAC_SIZE MAC_SIZE = LibSodium::MAC_SIZE
@ -24,11 +25,12 @@ module Cox::CryptoBox
@public_key = PublicKey.new pkey @public_key = PublicKey.new pkey
end end
def pair(public_key) # Return a Pair containing a precomputed shared secret for use with encryption/decryption.
def pair(public_key) : Pair
Pair.new self, public_key Pair.new self, public_key
end end
# Create a new pair and automatically close when exiting the block. # Create a new pair and automatically close when the block exits.
def pair(public_key) def pair(public_key)
pa = pair public_key pa = pair public_key
begin begin

View File

@ -35,6 +35,7 @@ module Cox
fun crypto_generichash_blake2b_keybytes_max : LibC::SizeT fun crypto_generichash_blake2b_keybytes_max : LibC::SizeT
fun crypto_generichash_blake2b_saltbytes : LibC::SizeT fun crypto_generichash_blake2b_saltbytes : LibC::SizeT
fun crypto_generichash_blake2b_personalbytes : LibC::SizeT fun crypto_generichash_blake2b_personalbytes : LibC::SizeT
fun sodium_memzero(Pointer(LibC::UChar), LibC::SizeT) : Nil
PUBLIC_KEY_SIZE = crypto_box_publickeybytes() PUBLIC_KEY_SIZE = crypto_box_publickeybytes()
SECRET_KEY_SIZE = crypto_box_secretkeybytes() SECRET_KEY_SIZE = crypto_box_secretkeybytes()

View File

@ -1,22 +1,35 @@
require "./lib_sodium" require "./lib_sodium"
module Cox module Cox
class SecretKey < Key # [https://libsodium.gitbook.io/doc/secret-key_cryptography](https://libsodium.gitbook.io/doc/secret-key_cryptography)
property bytes : Bytes #
# ```crystal
# key = Cox::SecretKey.new
# message = "foobar"
# encrypted, nonce = key.encrypt_easy message
#
# # On the other side.
# key = Cox::SecretKey.new key
# message = key.decrypt_easy encrypted, nonce
# ```
class SecretBox < Key
KEY_SIZE = LibSodium::SECRET_KEY_SIZE KEY_SIZE = LibSodium::SECRET_KEY_SIZE
MAC_SIZE = LibSodium::MAC_SIZE MAC_SIZE = LibSodium::MAC_SIZE
property bytes : Bytes
# Generate a new random key.
def initialize
@bytes = Random::Secure.random_bytes(KEY_SIZE)
end
# Use an existing key from bytes.
def initialize(@bytes : Bytes) def initialize(@bytes : Bytes)
if bytes.bytesize != KEY_SIZE if bytes.bytesize != KEY_SIZE
raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}") raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
end end
end end
def self.random
new Random::Secure.random_bytes(KEY_SIZE)
end
def encrypt_easy(data) def encrypt_easy(data)
encrypt_easy data.to_slice encrypt_easy data.to_slice
end end

View File

@ -2,6 +2,7 @@ require "../lib_sodium"
module Cox module Cox
class Sign::PublicKey < Key class Sign::PublicKey < Key
include Wipe
KEY_SIZE = LibSodium::PUBLIC_SIGN_SIZE KEY_SIZE = LibSodium::PUBLIC_SIGN_SIZE
getter bytes : Bytes getter bytes : Bytes

View File

@ -2,6 +2,7 @@ require "../lib_sodium"
module Cox module Cox
class Sign::SecretKey < Cox::Key class Sign::SecretKey < Cox::Key
include Wipe
KEY_SIZE = LibSodium::SECRET_SIGN_SIZE KEY_SIZE = LibSodium::SECRET_SIGN_SIZE
getter bytes : Bytes getter bytes : Bytes

18
src/cox/wipe.cr Normal file
View File

@ -0,0 +1,18 @@
module Cox::Wipe
@closed = false
def close
return if @closed
wipe
@closed = true
end
protected def wipe
return if @closed
Cox.memzero @bytes
end
def finalize
wipe # Don't call close. May be overridden with calls unsafe within finalize.
end
end