Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for key based encryption and decryption #3

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
320 changes: 207 additions & 113 deletions rncryptor.go
Original file line number Diff line number Diff line change
@@ -1,139 +1,233 @@
package rncryptor

import(
"bytes"
"errors"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/hmac"
"crypto/aes"
"crypto/cipher"
"golang.org/x/crypto/pbkdf2"
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"errors"
"fmt"
"golang.org/x/crypto/pbkdf2"
)

const (
blockSize = 16
supportedVersion = byte(3)
optionUsesPassword = byte(1)
hmacLength = 32
saltLength = 8
pbkdfIterations = 10000
keyByteLength = 32
)

func Decrypt(password string, data []byte) ([]byte, error) {
version := data[:1]
options := data[1:2]
encSalt := data[2:10]
hmacSalt := data[10:18]
iv := data[18:34]
cipherText := data[34:(len(data)-66+34)]
expectedHmac := data[len(data)-32:len(data)]

msg := make([]byte, 0)
msg = append(msg, version...)
msg = append(msg, options...)
msg = append(msg, encSalt...)
msg = append(msg, hmacSalt...)
msg = append(msg, iv...)
msg = append(msg, cipherText...)

hmacKey := pbkdf2.Key([]byte(password), hmacSalt, 10000, 32, sha1.New)
testHmac := hmac.New(sha256.New, hmacKey)
testHmac.Write(msg)
testHmacVal := testHmac.Sum(nil)

// its important to use hmac.Equal to not leak time
// information. See https://github.com/RNCryptor/RNCryptor-Spec
verified := hmac.Equal(testHmacVal, expectedHmac)

if !verified {
return nil, errors.New("Password may be incorrect, or the data has been corrupted. (HMAC could not be verified)")
}

cipherKey := pbkdf2.Key([]byte(password), encSalt, 10000, 32, sha1.New)
cipherBlock, err := aes.NewCipher(cipherKey)
if err != nil {
return nil, err
}

decrypted := make([]byte, len(cipherText))
copy(decrypted, cipherText)
decrypter := cipher.NewCBCDecrypter(cipherBlock, iv)
decrypter.CryptBlocks(decrypted, decrypted)

// un-padd decrypted data
length := len(decrypted)
unpadding := int(decrypted[length-1])

return decrypted[:(length - unpadding)], nil
version := data[0]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the causes of #1 I think, but it might be nice to check the length up front and return an error if the length is shorter than the expected header instead of causing a panic.

Possibly something like:

if len(data) < versionLength + optionsLen + saltLength + hmacSaltLength + ivLength + hmacLength {
    return nil, errors.New("invalid data: does not contain valid header")
}


if version != supportedVersion {
return nil, errors.New(fmt.Sprintf("unsupported version: %d", version))
}

options := data[1]
if options&optionUsesPassword == 0 {
return nil, errors.New("cannot decrypt key-based encryption with password")
}

encSalt := data[2:10]
hmacSalt := data[10:18]
iv := data[18:34]
cipherText := data[34:(len(data) - hmacLength)]
expectedHmac := data[len(data)-hmacLength:]

hmacKey := pbkdf2.Key([]byte(password), hmacSalt, pbkdfIterations, keyByteLength, sha1.New)
testHmac := hmac.New(sha256.New, hmacKey)
testHmac.Write(data[:len(data)-hmacLength])
testHmacVal := testHmac.Sum(nil)

// its important to use hmac.Equal to not leak time
// information. See https://github.com/RNCryptor/RNCryptor-Spec
verified := hmac.Equal(testHmacVal, expectedHmac)

if !verified {
return nil, errors.New("password may be incorrect, or the data has been corrupted: (HMAC could not be verified)")
}

cipherKey := pbkdf2.Key([]byte(password), encSalt, pbkdfIterations, keyByteLength, sha1.New)
cipherBlock, err := aes.NewCipher(cipherKey)
if err != nil {
return nil, err
}

decrypted := make([]byte, len(cipherText))
copy(decrypted, cipherText)
decrypter := cipher.NewCBCDecrypter(cipherBlock, iv)
decrypter.CryptBlocks(decrypted, decrypted)

// un-padd decrypted data
length := len(decrypted)
unpadding := int(decrypted[length-1])

return decrypted[:(length - unpadding)], nil
}

func DecryptWithKey(decKey, hmacKey, data []byte) ([]byte, error) {
version := data[0]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same suggestion as above about checking the length before accessing the slice.


if version != supportedVersion {
return nil, errors.New(fmt.Sprintf("unsupported version: %d", version))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return nil, errors.New(fmt.Sprintf("unsupported version: %d", version))
return nil, fmt.Errorf("unsupported version: %d", version)

}

options := data[1]
if options&optionUsesPassword != 0 {
return nil, errors.New("cannot decrypt password-encrypted data with key")
}

iv := data[2:18]
cipherText := data[18 : len(data)-hmacLength]
expectedHmac := data[len(data)-hmacLength:]

testHmac := hmac.New(sha256.New, hmacKey)
testHmac.Write(data[:len(data)-32])
testHmacVal := testHmac.Sum(nil)

// its important to use hmac.Equal to not leak time
// information. See https://github.com/RNCryptor/RNCryptor-Spec
verified := hmac.Equal(testHmacVal, expectedHmac)

if !verified {
return nil, errors.New("key may be incorrect, or the data has been corrupted: (HMAC could not be verified)")
}

cipherBlock, err := aes.NewCipher(decKey)
if err != nil {
return nil, err
}

decrypted := make([]byte, len(cipherText))
copy(decrypted, cipherText)
decrypter := cipher.NewCBCDecrypter(cipherBlock, iv)
decrypter.CryptBlocks(decrypted, decrypted)

// un-padd decrypted data
length := len(decrypted)
unpadding := int(decrypted[length-1])

return decrypted[:(length - unpadding)], nil
}

func Encrypt(password string, data []byte) ([]byte, error) {
encSalt, encSaltErr := RandBytes(8)
if encSaltErr != nil {
return nil, encSaltErr
}

hmacSalt, hmacSaltErr := RandBytes(8)
if hmacSaltErr != nil {
return nil, hmacSaltErr
}

iv, ivErr := RandBytes(16)
if ivErr != nil {
return nil, ivErr
}

encrypted, encErr := EncryptWithOptions(password, data, encSalt, hmacSalt, iv)
if encErr != nil {
return nil, encErr
}
return encrypted, nil
encSalt, encSaltErr := RandBytes(saltLength)
if encSaltErr != nil {
return nil, encSaltErr
}

hmacSalt, hmacSaltErr := RandBytes(saltLength)
if hmacSaltErr != nil {
return nil, hmacSaltErr
}

iv, ivErr := RandBytes(blockSize)
if ivErr != nil {
return nil, ivErr
}

encrypted, encErr := EncryptWithOptions(password, data, encSalt, hmacSalt, iv)
if encErr != nil {
return nil, encErr
}
return encrypted, nil
}

func EncryptWithOptions(password string, data, encSalt, hmacSalt, iv []byte) ([]byte, error) {
if len(password) < 1 {
return nil, errors.New("Password cannot be empty")
}
if len(password) < 1 {
return nil, errors.New("password cannot be empty")
}

encKey := pbkdf2.Key([]byte(password), encSalt, pbkdfIterations, keyByteLength, sha1.New)
hmacKey := pbkdf2.Key([]byte(password), hmacSalt, pbkdfIterations, keyByteLength, sha1.New)

version := supportedVersion
options := optionUsesPassword

msg := make([]byte, 0)
msg = append(msg, version)
msg = append(msg, options)
msg = append(msg, encSalt...)
msg = append(msg, hmacSalt...)
msg = append(msg, iv...)

ciphertext, hmacValue, err := encryptAndHmac(msg, data, iv, encKey, hmacKey)
if err != nil {
return nil, err
}

msg = append(msg, ciphertext...)
msg = append(msg, hmacValue...)
return msg, nil
}

encKey := pbkdf2.Key([]byte(password), encSalt, 10000, 32, sha1.New)
hmacKey := pbkdf2.Key([]byte(password), hmacSalt, 10000, 32, sha1.New)
func EncryptWithKey(encKey, hmacKey, data []byte) ([]byte, error) {
iv, err := RandBytes(blockSize)
if err != nil {
return nil, err
}

cipherText := make([]byte, len(data))
copy(cipherText, data)
return EncryptWithKeyAndIv(encKey, hmacKey, iv, data)
}

func EncryptWithKeyAndIv(encKey, hmacKey, iv, data []byte) ([]byte, error) {

version := supportedVersion
options := byte(0)

version := byte(3)
options := byte(1)
msg := make([]byte, 0)
msg = append(msg, version)
msg = append(msg, options)
msg = append(msg, iv...)

msg := make([]byte, 0)
msg = append(msg, version)
msg = append(msg, options)
msg = append(msg, encSalt...)
msg = append(msg, hmacSalt...)
msg = append(msg, iv...)
ciphertext, hmacValue, err := encryptAndHmac(msg, data, iv, encKey, hmacKey)
if err != nil {
return nil, err
}

msg = append(msg, ciphertext...)
msg = append(msg, hmacValue...)
return msg, nil
}

cipherBlock, cipherBlockErr := aes.NewCipher(encKey)
if cipherBlockErr != nil {
return nil, cipherBlockErr
}
func encryptAndHmac(header, data, iv, encKey, hmacKey []byte) ([]byte, []byte, error) {
cipherBlock, err := aes.NewCipher(encKey)
if err != nil {
return nil, nil, err
}

// padd text for encryption
blockSize := cipherBlock.BlockSize()
padding := blockSize - len(cipherText)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
cipherText = append(cipherText, padText...)
// pad text for encryption
cipherText := make([]byte, len(data))
copy(cipherText, data)

encrypter := cipher.NewCBCEncrypter(cipherBlock, iv)
encrypter.CryptBlocks(cipherText, cipherText)
padding := blockSize - (len(cipherText) % blockSize)
padText := bytes.Repeat([]byte{byte(padding)}, padding)
cipherText = append(cipherText, padText...)

msg = append(msg, cipherText...)
encrypter := cipher.NewCBCEncrypter(cipherBlock, iv)
encrypter.CryptBlocks(cipherText, cipherText)

hmacSrc := hmac.New(sha256.New, hmacKey)
hmacSrc.Write(msg)
hmacVal := hmacSrc.Sum(nil)
msg := append(header, cipherText...)

msg = append(msg, hmacVal...)
hmacSrc := hmac.New(sha256.New, hmacKey)
hmacSrc.Write(msg)
hmacVal := hmacSrc.Sum(nil)

return msg, nil
return cipherText, hmacVal, nil
}

func RandBytes(num int64) ([]byte, error) {
bits := make([]byte, num)
_, err := rand.Read(bits)
if err != nil {
return nil, err
}
return bits, nil
bits := make([]byte, num)
_, err := rand.Read(bits)
if err != nil {
return nil, err
}
return bits, nil
}
Loading