github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/otto/encrypt.go (about)

     1  package otto
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/aes"
     6  	"crypto/cipher"
     7  	"crypto/rand"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  
    13  	"golang.org/x/crypto/scrypt"
    14  )
    15  
    16  const (
    17  	cryptPrefixV0   = "v0:"
    18  	cryptKeySaltLen = 32
    19  )
    20  
    21  // cryptWrite is a helper to encrypt data and then write it to a file.
    22  // Encryption is done by using bcrypt as a KDF followed by AES-GCM.
    23  func cryptWrite(dst string, password string, plaintext []byte) error {
    24  	keySalt := make([]byte, cryptKeySaltLen)
    25  	if _, err := rand.Read(keySalt); err != nil {
    26  		return err
    27  	}
    28  
    29  	key, err := scrypt.Key([]byte(password), keySalt, 16384, 8, 1, 32)
    30  	if err != nil {
    31  		return err
    32  	}
    33  
    34  	aesCipher, err := aes.NewCipher(key)
    35  	if err != nil {
    36  		return err
    37  	}
    38  
    39  	gcm, err := cipher.NewGCM(aesCipher)
    40  	if err != nil {
    41  		return err
    42  	}
    43  
    44  	// Compute random nonce
    45  	nonce := make([]byte, gcm.NonceSize())
    46  	if _, err := rand.Read(nonce); err != nil {
    47  		return err
    48  	}
    49  
    50  	// Encrypt and tag with GCM
    51  	out := gcm.Seal(nil, nonce, plaintext, nil)
    52  	ciphertext := make([]byte, 0, len("v0:")+len(nonce)+len(out))
    53  	ciphertext = append(ciphertext, []byte("v0:")...)
    54  	ciphertext = append(ciphertext, keySalt...)
    55  	ciphertext = append(ciphertext, nonce...)
    56  	ciphertext = append(ciphertext, out...)
    57  	out = nil
    58  
    59  	// Create the file for writing, making sure it is opened as 0600
    60  	// for a little additional security.
    61  	f, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
    62  	if err != nil {
    63  		return err
    64  	}
    65  	defer f.Close()
    66  
    67  	_, err = io.Copy(f, bytes.NewReader(ciphertext))
    68  	return err
    69  }
    70  
    71  func cryptRead(path string, password string) ([]byte, error) {
    72  	// Read the contents of the path first
    73  	ciphertext, err := ioutil.ReadFile(path)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	// Verify that the data looks valid
    79  	if !bytes.HasPrefix(ciphertext, []byte(cryptPrefixV0)) {
    80  		return nil, fmt.Errorf("corrupt encrypted data")
    81  	}
    82  
    83  	// Read our key salt
    84  	ciphertext = ciphertext[len(cryptPrefixV0):]
    85  	keySalt := ciphertext[:32]
    86  	ciphertext = ciphertext[32:]
    87  
    88  	// Derive the key
    89  	key, err := scrypt.Key([]byte(password), keySalt, 16384, 8, 1, 32)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	// Setup the cipher
    95  	aesCipher, err := aes.NewCipher(key)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	// Setup the GCM AEAD
   101  	gcm, err := cipher.NewGCM(aesCipher)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	// Get the nonce and ciphertext out
   107  	nonce := ciphertext[:gcm.NonceSize()]
   108  	ciphertext = ciphertext[gcm.NonceSize():]
   109  
   110  	// Decrypt
   111  	return gcm.Open(nil, nonce, ciphertext, nil)
   112  }