age

Simple, secure encryption with UNIX-style composability.
git clone git://git.sgregoratto.me/age
Log | Files | Refs | README | LICENSE

scrypt.go (3750B)


      1 // Copyright 2019 Google LLC
      2 //
      3 // Use of this source code is governed by a BSD-style
      4 // license that can be found in the LICENSE file or at
      5 // https://developers.google.com/open-source/licenses/bsd
      6 
      7 package age
      8 
      9 import (
     10 	"crypto/rand"
     11 	"errors"
     12 	"fmt"
     13 	"strconv"
     14 
     15 	"github.com/FiloSottile/age/internal/format"
     16 	"golang.org/x/crypto/chacha20poly1305"
     17 	"golang.org/x/crypto/scrypt"
     18 )
     19 
     20 type ScryptRecipient struct {
     21 	password   []byte
     22 	workFactor int
     23 }
     24 
     25 var _ Recipient = &ScryptRecipient{}
     26 
     27 func (*ScryptRecipient) Type() string { return "scrypt" }
     28 
     29 func NewScryptRecipient(password string) (*ScryptRecipient, error) {
     30 	if len(password) == 0 {
     31 		return nil, errors.New("empty scrypt password")
     32 	}
     33 	r := &ScryptRecipient{
     34 		password: []byte(password),
     35 		// TODO: automatically scale this to 1s (with a min) in the CLI.
     36 		workFactor: 18, // 1s on a modern machine
     37 	}
     38 	return r, nil
     39 }
     40 
     41 // SetWorkFactor sets the scrypt work factor to 2^logN.
     42 // It must be called before Wrap.
     43 func (r *ScryptRecipient) SetWorkFactor(logN int) {
     44 	if logN > 30 || logN < 1 {
     45 		panic("age: SetWorkFactor called with illegal value")
     46 	}
     47 	r.workFactor = logN
     48 }
     49 
     50 func (r *ScryptRecipient) Wrap(fileKey []byte) (*format.Recipient, error) {
     51 	salt := make([]byte, 16)
     52 	if _, err := rand.Read(salt[:]); err != nil {
     53 		return nil, err
     54 	}
     55 
     56 	logN := r.workFactor
     57 	l := &format.Recipient{
     58 		Type: "scrypt",
     59 		Args: []string{format.EncodeToString(salt), strconv.Itoa(logN)},
     60 	}
     61 
     62 	k, err := scrypt.Key(r.password, salt, 1<<logN, 8, 1, chacha20poly1305.KeySize)
     63 	if err != nil {
     64 		return nil, fmt.Errorf("failed to generate scrypt hash: %v", err)
     65 	}
     66 
     67 	wrappedKey, err := aeadEncrypt(k, fileKey)
     68 	if err != nil {
     69 		return nil, err
     70 	}
     71 	l.Body = []byte(format.EncodeToString(wrappedKey) + "\n")
     72 
     73 	return l, nil
     74 }
     75 
     76 type ScryptIdentity struct {
     77 	password      []byte
     78 	maxWorkFactor int
     79 }
     80 
     81 var _ Identity = &ScryptIdentity{}
     82 
     83 func (*ScryptIdentity) Type() string { return "scrypt" }
     84 
     85 func NewScryptIdentity(password string) (*ScryptIdentity, error) {
     86 	if len(password) == 0 {
     87 		return nil, errors.New("empty scrypt password")
     88 	}
     89 	i := &ScryptIdentity{
     90 		password:      []byte(password),
     91 		maxWorkFactor: 22, // 15s on a modern machine
     92 	}
     93 	return i, nil
     94 }
     95 
     96 // SetWorkFactor sets the maximum accepted scrypt work factor to 2^logN.
     97 // It must be called before Unwrap.
     98 func (i *ScryptIdentity) SetMaxWorkFactor(logN int) {
     99 	if logN > 30 || logN < 1 {
    100 		panic("age: SetMaxWorkFactor called with illegal value")
    101 	}
    102 	i.maxWorkFactor = logN
    103 }
    104 
    105 func (i *ScryptIdentity) Unwrap(block *format.Recipient) ([]byte, error) {
    106 	if block.Type != "scrypt" {
    107 		return nil, errors.New("wrong recipient block type")
    108 	}
    109 	if len(block.Args) != 2 {
    110 		return nil, errors.New("invalid scrypt recipient block")
    111 	}
    112 	salt, err := format.DecodeString(block.Args[0])
    113 	if err != nil {
    114 		return nil, fmt.Errorf("failed to parse scrypt salt: %v", err)
    115 	}
    116 	if len(salt) != 16 {
    117 		return nil, errors.New("invalid scrypt recipient block")
    118 	}
    119 	logN, err := strconv.Atoi(block.Args[1])
    120 	if err != nil {
    121 		return nil, fmt.Errorf("failed to parse scrypt work factor: %v", err)
    122 	}
    123 	if logN > i.maxWorkFactor {
    124 		return nil, fmt.Errorf("scrypt work factor too large: %v", logN)
    125 	}
    126 	if logN <= 0 {
    127 		return nil, fmt.Errorf("invalid scrypt work factor: %v", logN)
    128 	}
    129 	wrappedKey, err := format.DecodeString(string(block.Body))
    130 	if err != nil {
    131 		return nil, fmt.Errorf("failed to parse scrypt recipient: %v", err)
    132 	}
    133 
    134 	k, err := scrypt.Key(i.password, salt, 1<<logN, 8, 1, chacha20poly1305.KeySize)
    135 	if err != nil {
    136 		return nil, fmt.Errorf("failed to generate scrypt hash: %v", err)
    137 	}
    138 
    139 	fileKey, err := aeadDecrypt(k, wrappedKey)
    140 	if err != nil {
    141 		return nil, fmt.Errorf("failed to decrypt file key: %v", err)
    142 	}
    143 	return fileKey, nil
    144 }