Skip to content

Commit

Permalink
Secure Cell passphrase API: GoThemis (#625)
Browse files Browse the repository at this point in the history
* Passphrase API for Seal mode

Introduce a new constructor "SealWithPassphrase" which returns a
different instance of Secure Cell that works with passphrases instead of
symmetric keys, using appropriate Themis Core API.

Note that we do not convert the string encoding here. Go's "string" type
is expected to contain UTF-8 data (consistent with what other wrappers
use). However, it can contain arbitrary byte sequences too. We do not
interpret them in any way (e.g., as []rune or whatever) and just use
the bytes as is.

* Integration test for GoThemis passphrase API
* Code sample for passphrase API
* Mention new API in CHANGELOG
  • Loading branch information
ilammy committed Apr 24, 2020
1 parent f087453 commit 81d8baf
Show file tree
Hide file tree
Showing 6 changed files with 657 additions and 5 deletions.
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,31 @@ _Code:_

This API is less ambiguous and more convenient to use.

- GoThemis now supports _passphrase API_ in Seal mode ([#625](https://github.com/cossacklabs/themis/pull/625)).

```go
scell, err := cell.SealWithPassphrase("secret")
if err != nil {
return err
}

encrypted, err := scell.Encrypt([]byte("message"), nil)
if err != nil {
return err
}

decrypted, err := scell.Decrypt(encrypted, nil)
if err != nil {
return err
}
```

You can safely and securely use short, human-readable passphrases as strings with this new API.

Existing master key API (`cell.SealWithKey()` or `cell.New()`) should not be used with passphrases or passwords.
Use master key API with symmetric encryption keys, such as generated by `keys.NewSymmetricKey()` ([#561](https://github.com/cossacklabs/themis/pull/561)).
Use passphrase API with human-readable passphrases.

- **Deprecated API**

- Run-time mode-setting for Secure Cell is deprecated ([#624](https://github.com/cossacklabs/themis/pull/624)).
Expand Down
28 changes: 28 additions & 0 deletions docs/examples/go/secure_cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

func main() {
// Cryptographic parameters. Keep them secret.
passphrase := "broccoli"
key, err := keys.NewSymmetricKey()
if err != nil {
fmt.Fprintf(os.Stderr, "cannot generate symmetric key: %v\n", err)
Expand Down Expand Up @@ -66,6 +67,33 @@ func main() {
fmt.Printf("Decrypted: %s\n", string(decrypted))
fmt.Printf("\n")

fmt.Println("Secure Cell - Seal mode (passphrase)")
// This is the easiest mode to use if you need to keep the secret in your head.

scellPW, err := cell.SealWithPassphrase(passphrase)
if err != nil {
fmt.Fprintf(os.Stderr, "cannot construct Secure Cell: %v\n", err)
os.Exit(1)
}
encrypted, err = scellPW.Encrypt(message, context)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to encrypt message: %v\n", err)
os.Exit(1)
}
decrypted, err = scellPW.Decrypt(encrypted, context)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to decrypt message: %v\n", err)
os.Exit(1)
}
if !bytes.Equal(decrypted, message) {
fmt.Fprintf(os.Stderr, "decrypted content does not match: %s\n", string(decrypted))
os.Exit(1)
}
fmt.Printf("Encoded: %s\n", base64.StdEncoding.EncodeToString([]byte(message)))
fmt.Printf("Encrypted: %s\n", base64.StdEncoding.EncodeToString(encrypted))
fmt.Printf("Decrypted: %s\n", string(decrypted))
fmt.Printf("\n")

fmt.Println("Secure Cell - Token Protect mode")
// A bit harded to use than Seal mode due to extra "authentication token",
// but the encrypted data has the same size as input.
Expand Down
11 changes: 6 additions & 5 deletions gothemis/cell/cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,12 @@ import (

// Errors returned by Secure Cell.
var (
ErrInvalidMode = errors.NewWithCode(errors.InvalidParameter, "invalid Secure Cell mode specified")
ErrMissingKey = errors.NewWithCode(errors.InvalidParameter, "empty symmetric key for Secure Cell")
ErrMissingMessage = errors.NewWithCode(errors.InvalidParameter, "empty message for Secure Cell")
ErrMissingToken = errors.NewWithCode(errors.InvalidParameter, "authentication token is required in Token Protect mode")
ErrMissingContext = errors.NewWithCode(errors.InvalidParameter, "associated context is required in Context Imprint mode")
ErrInvalidMode = errors.NewWithCode(errors.InvalidParameter, "invalid Secure Cell mode specified")
ErrMissingKey = errors.NewWithCode(errors.InvalidParameter, "empty symmetric key for Secure Cell")
ErrMissingPassphrase = errors.NewWithCode(errors.InvalidParameter, "empty passphrase for Secure Cell")
ErrMissingMessage = errors.NewWithCode(errors.InvalidParameter, "empty message for Secure Cell")
ErrMissingToken = errors.NewWithCode(errors.InvalidParameter, "authentication token is required in Token Protect mode")
ErrMissingContext = errors.NewWithCode(errors.InvalidParameter, "associated context is required in Context Imprint mode")
)

// Secure Cell operation mode.
Expand Down
145 changes: 145 additions & 0 deletions gothemis/cell/seal_pw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright (c) 2020 Cossack Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cell

/*
#cgo LDFLAGS: -lthemis
#include <themis/themis.h>
*/
import "C"

import (
"github.com/cossacklabs/themis/gothemis/errors"
)

// SecureCellSealPassphrase is Secure Cell in Seal mode.
// This is the most secure and easy way to protect stored data.
//
// The data is protected by a secret passphrase.
// You can safely use relatively short, human-readable passwords with this mode.
//
// If you do not need to keep the secret in the head and would rather write it into a file,
// consider using symmetric keys.
// See ``cell.SealWithKey()'' and ``SecureCellSeal''.
//
// Read more about Seal mode here:
//
// https://docs.cossacklabs.com/pages/secure-cell-cryptosystem/#seal-mode
type SecureCellSealPassphrase struct {
passphrase string
}

// SealWithPassphrase makes a new Secure Cell in Seal mode secured by a passphrase.
func SealWithPassphrase(passphrase string) (*SecureCellSealPassphrase, error) {
if len(passphrase) == 0 {
return nil, ErrMissingPassphrase
}
return &SecureCellSealPassphrase{passphrase}, nil
}

// Encrypt message.
//
// Message is encrypted and authentication token is appended to it,
// forming a single sealed buffer.
//
// The context, if provided, is cryptographically mixed with the data,
// but is not included into the resulting encrypted message.
// You will have to provide the same context again during decryption.
// Usually this is some plaintext data associated with encrypted data,
// such as database row number, protocol message ID, etc.
// Empty and nil contexts are identical.
//
// Encrypted data is returned as a single byte slice.
//
// An error is returned on failure, such as if the message is empty,
// or in case of some internal failure in cryptographic backend.
func (sc *SecureCellSealPassphrase) Encrypt(message, context []byte) ([]byte, error) {
if len(message) == 0 {
return nil, ErrMissingMessage
}

length, err := encryptSealWithPassphrase(sc.passphrase, message, context, nil)
if err != errors.BufferTooSmall {
return nil, errors.NewWithCode(err, "Secure Cell failed to encrypt")
}
encrypted := make([]byte, length)
length, err = encryptSealWithPassphrase(sc.passphrase, message, context, encrypted)
if err != errors.Success {
return nil, errors.NewWithCode(err, "Secure Cell failed to encrypt")
}
return encrypted[:length], nil
}

// Decrypt message.
//
// Secure Cell validates association with the context data, decrypts the message,
// and verifies its integrity using authentication data embedded into the message.
//
// You need to provide the same context as used during encryption.
// (If there was no context you can use empty or nil value).
//
// Non-empty decrypted data is returned if everything goes well.
//
// An error will be returned on failure, such as if the message is empty,
// or if the data has been tampered with,
// or if the secret or associated context do not match the ones used for encryption.
func (sc *SecureCellSealPassphrase) Decrypt(encrypted, context []byte) ([]byte, error) {
if len(encrypted) == 0 {
return nil, ErrMissingMessage
}

length, err := decryptSealWithPassphrase(sc.passphrase, encrypted, context, nil)
if err != errors.BufferTooSmall {
return nil, errors.NewWithCode(err, "Secure Cell failed to decrypt")
}
decrypted := make([]byte, length)
length, err = decryptSealWithPassphrase(sc.passphrase, encrypted, context, decrypted)
if err != errors.Success {
return nil, errors.NewWithCode(err, "Secure Cell failed to decrypt")
}
return decrypted[:length], nil
}

func encryptSealWithPassphrase(passphrase string, plaintext, userContext, ciphertext []byte) (int, errors.ThemisErrorCode) {
ciphertextLength := bytesSize(ciphertext)
err := C.themis_secure_cell_encrypt_seal_with_passphrase(
bytesData([]byte(passphrase)),
bytesSize([]byte(passphrase)),
bytesData(userContext),
bytesSize(userContext),
bytesData(plaintext),
bytesSize(plaintext),
bytesData(ciphertext),
&ciphertextLength,
)
return int(ciphertextLength), errors.ThemisErrorCode(err)
}

func decryptSealWithPassphrase(passphrase string, ciphertext, userContext, plaintext []byte) (int, errors.ThemisErrorCode) {
plaintextLength := bytesSize(plaintext)
err := C.themis_secure_cell_decrypt_seal_with_passphrase(
bytesData([]byte(passphrase)),
bytesSize([]byte(passphrase)),
bytesData(userContext),
bytesSize(userContext),
bytesData(ciphertext),
bytesSize(ciphertext),
bytesData(plaintext),
&plaintextLength,
)
return int(plaintextLength), errors.ThemisErrorCode(err)
}
Loading

0 comments on commit 81d8baf

Please sign in to comment.