diff --git a/README.md b/README.md index e42eb6a..c768fdf 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Crystal bindings for the [libsodium API](https://libsodium.gitbook.io/doc/) - [x] [XChaCha20-Poly1305-IETF](https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction) - [ ] [ChaCha20-Poly1305-IETF](https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/chacha20-poly1305/ietf_chacha20-poly1305_construction) - [ ] [ChaCha20-Poly1305](https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/chacha20-poly1305) + - [x] Combined and detached mode - [Hashing](https://libsodium.gitbook.io/doc/hashing) - [x] ☑ [Blake2b](https://libsodium.gitbook.io/doc/hashing/generic_hashing) - [x] Complete libsodium implementation including `key`, `salt`, `personal` and fully selectable output sizes. diff --git a/spec/sodium/cipher/aead/chalsa_spec.cr b/spec/sodium/cipher/aead/chalsa_spec.cr index f436959..3fe2c33 100644 --- a/spec/sodium/cipher/aead/chalsa_spec.cr +++ b/spec/sodium/cipher/aead/chalsa_spec.cr @@ -29,18 +29,43 @@ end {% for name in %w(Xchacha20Poly1305Ietf) %} # TODO: verify against test vectors. describe Sodium::Cipher::Aead::{{ name.id }} do - it "encrypts/decrypts" do + it "encrypts/decrypts in combined mode" do box = Sodium::Cipher::Aead::{{ name.id }}.new - message = "foobar" - additional = "foo" - mac, encrypted, nonce = box.encrypt_detached message, additional: additional - decrypted = box.decrypt_detached encrypted, nonce: nonce, mac: mac, additional: additional - message.should eq String.new(decrypted) + message = "foo" + additional = "bar" + encrypted, nonce = box.encrypt message, additional: additional + decrypted = box.decrypt_string encrypted, nonce: nonce, additional: additional + decrypted.should eq message # Wrong additional. expect_raises(Sodium::Error::DecryptionFailed) do - box.decrypt_detached encrypted, nonce: nonce, mac: mac, additional: "bar".to_slice + box.decrypt encrypted, nonce: nonce, additional: "baz".to_slice + end + + # Missing additional. + expect_raises(Sodium::Error::DecryptionFailed) do + box.decrypt encrypted, nonce: nonce + end + + # Wrong data. + expect_raises(Sodium::Error::DecryptionFailed) do + box.decrypt "badmsgbadmsgbadmsgbadmsgbadmsg".to_slice, nonce: nonce + end + end + + it "encrypts/decrypts in detached mode" do + box = Sodium::Cipher::Aead::{{ name.id }}.new + + message = "foo" + additional = "bar" + mac, encrypted, nonce = box.encrypt_detached message, additional: additional + decrypted = box.decrypt_detached_string encrypted, nonce: nonce, mac: mac, additional: additional + decrypted.should eq message + + # Wrong additional. + expect_raises(Sodium::Error::DecryptionFailed) do + box.decrypt_detached encrypted, nonce: nonce, mac: mac, additional: "baz".to_slice end # Missing additional. @@ -57,7 +82,7 @@ end it "can't encrypt twice using the same nonce" do box = Sodium::Cipher::Aead::{{ name.id }}.new - message = "foobar" + message = "foo" mac, encrypted, nonce = box.encrypt_detached message expect_raises(Sodium::Nonce::Error::Reused) do diff --git a/src/sodium/cipher/aead/chalsa.cr b/src/sodium/cipher/aead/chalsa.cr index 7c98b56..0a67e8f 100644 --- a/src/sodium/cipher/aead/chalsa.cr +++ b/src/sodium/cipher/aead/chalsa.cr @@ -15,25 +15,56 @@ module Sodium::Cipher::Aead end # Encrypts data and returns {ciphertext, nonce} - def encrypt(data) - encrypt data.to_slice - end - - # Encrypts data and returns {mac, ciphertext, nonce} - def encrypt_detached(data, dst : Bytes? = nil, *, mac : Bytes? = nil, additional = nil) - encrypt_detached data.to_slice, mac: mac, additional: additional + def encrypt(src, dst : Bytes? = nil, *, nonce = nil, additional = nil) + {Bytes, Nonce} + offset = src.bytesize + dst ||= Bytes.new (offset + mac_size) + mac = dst[offset, mac_size] + _, _, nonce = encrypt_detached src.to_slice, dst[0, offset], mac: mac, nonce: nonce, additional: additional + {dst, nonce} end # Decrypts data and returns plaintext + def decrypt(src, dst : Bytes? = nil, *, nonce : Nonce, additional = nil) : Bytes + src = src.to_slice + offset = src.bytesize - mac_size + mac = src[offset, mac_size] + + decrypt_detached src[0, offset], dst, nonce: nonce, mac: mac, additional: additional + end + + # Decrypts data and returns plaintext + def decrypt_string(src, dst : Bytes? = nil, *, nonce : Nonce, additional = nil) : String + buf = decrypt src, dst, nonce: nonce, additional: additional + # TODO: optimize + String.new buf + end + + # Encrypts `src` and returns {mac, ciphertext, nonce} + def encrypt_detached(src, dst : Bytes? = nil, *, nonce = nil, mac : Bytes? = nil, additional = nil) : {Bytes, Bytes, Nonce} + encrypt_detached src.to_slice, mac: mac, nonce: nonce, additional: additional + end + + # Decrypts `src` and returns plaintext # Must supply `mac` and `nonce` # Must supply `additional` if supplied to #encrypt - def decrypt_detached(data, dst : Bytes? = nil, *, mac : Bytes? = nil, additional = nil) - encrypt_detached data.to_slice, mac: mac, additional: additional + def decrypt_detached(src, dst : Bytes? = nil, *, nonce = nil, mac : Bytes? = nil, additional = nil) : Bytes + decrypt_detached src.to_slice, mac: mac, nonce: nonce, additional: additional + end + + # Decrypts `src` and returns plaintext + # Must supply `mac` and `nonce` + # Must supply `additional` if supplied to #encrypt + def decrypt_detached_string(src, dst : Bytes? = nil, *, nonce = nil, mac : Bytes? = nil, additional = nil) : String + buf = decrypt_detached src.to_slice, dst, mac: mac, nonce: nonce, additional: additional + # TODO: optimize + String.new buf end abstract def encrypt_detached(src : Bytes, dst : Bytes? = nil, *, nonce : Sodium::Nonce? = nil, mac : Bytes? = nil, additional : String | Bytes | Nil = nil) : {Bytes, Bytes, Sodium::Nonce} abstract def decrypt_detached(src : Bytes, dst : Bytes? = nil, *, nonce : Sodium::Nonce, mac : Bytes, additional : String | Bytes | Nil = nil) : Bytes protected abstract def key_size : Int32 + protected abstract def mac_size : Int32 end {% for key, val in {"Xchacha20Poly1305Ietf" => "_xchacha20poly1305_ietf"} %} @@ -53,7 +84,7 @@ module Sodium::Cipher::Aead # May supply `mac`, otherwise a new one is returned. # May supply `additional` def encrypt_detached(src : Bytes, dst : Bytes? = nil, nonce : Sodium::Nonce? = nil, *, mac : Bytes? = nil, additional : String | Bytes | Nil = nil) : {Bytes, Bytes, Sodium::Nonce} - dst ||= Bytes.new(src.bytesize) + dst ||= Bytes.new src.bytesize nonce ||= Sodium::Nonce.random mac ||= Bytes.new MAC_SIZE @@ -93,6 +124,10 @@ module Sodium::Cipher::Aead protected def key_size KEY_SIZE end + + protected def mac_size + MAC_SIZE + end end {% end %} end