-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Added module
[encryption]
for simple cryptography utilities (#296)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> ### Description - Added module `[encryption]` for simple cryptography utilities - Support more hashing algorithm ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update).
- Loading branch information
1 parent
70ded98
commit efc7879
Showing
9 changed files
with
428 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
:sparkles: `[hashing]` Added support for [blake2b](https://www.blake2.net/) hashing algorithm |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
:sparkles: Added module `[encryption]` for simple cryptography utilities |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package encryption | ||
|
||
import ( | ||
"crypto/rand" | ||
"encoding/base64" | ||
"fmt" | ||
|
||
"golang.org/x/crypto/nacl/box" | ||
|
||
"github.com/ARM-software/golang-utils/utils/commonerrors" | ||
) | ||
|
||
const KeySize = 32 | ||
|
||
var ( | ||
errKeySize = fmt.Errorf("%w: recipient key has invalid size", commonerrors.ErrInvalid) | ||
) | ||
|
||
type keyPair struct { | ||
public string | ||
private string `json:"-"` | ||
} | ||
|
||
func (k *keyPair) String() string { | ||
return fmt.Sprintf("{Public: %v}", k.GetPublicKey()) | ||
} | ||
|
||
func (k *keyPair) GoString() string { | ||
return fmt.Sprintf("KeyPair(%q)", k.String()) | ||
} | ||
|
||
func (k *keyPair) MarshalJSON() ([]byte, error) { | ||
json := fmt.Sprintf("{\"Public\": %q}", k.GetPublicKey()) | ||
return []byte(json), nil | ||
} | ||
|
||
func (k *keyPair) GetPublicKey() string { | ||
return k.public | ||
} | ||
|
||
func (k *keyPair) GetPrivateKey() string { | ||
return k.private | ||
} | ||
|
||
func newKeyPair(public, private *[32]byte) (IKeyPair, error) { | ||
if public == nil || private == nil { | ||
return nil, fmt.Errorf("%w: missing key", commonerrors.ErrUndefined) | ||
} | ||
return newBasicKeyPair(base64.StdEncoding.EncodeToString((*public)[:]), base64.StdEncoding.EncodeToString((*private)[:])), nil | ||
} | ||
|
||
func newBasicKeyPair(public, private string) IKeyPair { | ||
return &keyPair{ | ||
public: public, | ||
private: private, | ||
} | ||
} | ||
|
||
// GenerateKeyPair generates a asymmetric key pair suitable for use with encryption utilities. Works with [NaCl box](https://nacl.cr.yp.to/box.html.) | ||
func GenerateKeyPair() (pair IKeyPair, err error) { | ||
pub, priv, err := box.GenerateKey(rand.Reader) | ||
if err != nil { | ||
err = fmt.Errorf("%w: could not generate keys: %v", commonerrors.ErrUnexpected, err.Error()) | ||
return | ||
} | ||
|
||
pair, err = newKeyPair(pub, priv) | ||
return | ||
} | ||
|
||
// EncryptWithPublicKey encrypts small messages using a 32-byte public key (See https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes) | ||
func EncryptWithPublicKey(base64EncodedPublicKey string, message string) (encryptedBase64Message string, err error) { | ||
decodedPublicKey, err := base64.StdEncoding.DecodeString(base64EncodedPublicKey) | ||
if err != nil { | ||
err = base64DecodingError(err) | ||
return | ||
} | ||
if len(decodedPublicKey) != KeySize { | ||
err = errKeySize | ||
return | ||
} | ||
|
||
recipientKey := [KeySize]byte{} | ||
copy(recipientKey[:], decodedPublicKey) | ||
|
||
secretBytes := []byte(message) | ||
|
||
encryptedBytes, err := box.SealAnonymous([]byte{}, secretBytes, &recipientKey, rand.Reader) | ||
if err != nil { | ||
err = fmt.Errorf("%w: box.SealAnonymous failed with error: %v", commonerrors.ErrUnexpected, err.Error()) | ||
return | ||
} | ||
|
||
encryptedBase64Message = base64.StdEncoding.EncodeToString(encryptedBytes) | ||
|
||
return | ||
} | ||
|
||
func base64DecodingError(err error) error { | ||
return fmt.Errorf("%w: base64.StdEncoding.DecodeString was unable to decode string: %v", commonerrors.ErrInvalid, err.Error()) | ||
} | ||
|
||
// DecryptWithKeyPair decrypts small base64 encoded messages | ||
func DecryptWithKeyPair(keys IKeyPair, base64EncodedEncryptedMessage string) (decryptedMessage string, err error) { | ||
if keys == nil { | ||
err = fmt.Errorf("%w: missing keys", commonerrors.ErrUndefined) | ||
return | ||
} | ||
decodedPublicKey, err := base64.StdEncoding.DecodeString(keys.GetPublicKey()) | ||
if err != nil { | ||
err = base64DecodingError(err) | ||
return | ||
} | ||
if len(decodedPublicKey) != KeySize { | ||
err = errKeySize | ||
return | ||
} | ||
decodedPrivateKey, err := base64.StdEncoding.DecodeString(keys.GetPrivateKey()) | ||
if err != nil { | ||
err = base64DecodingError(err) | ||
return | ||
} | ||
if len(decodedPrivateKey) != KeySize { | ||
err = errKeySize | ||
return | ||
} | ||
|
||
decodedMessage, err := base64.StdEncoding.DecodeString(base64EncodedEncryptedMessage) | ||
if err != nil { | ||
err = base64DecodingError(err) | ||
return | ||
} | ||
|
||
publicKey := [KeySize]byte{} | ||
copy(publicKey[:], decodedPublicKey) | ||
|
||
privateKey := [KeySize]byte{} | ||
copy(privateKey[:], decodedPrivateKey) | ||
|
||
message, ok := box.OpenAnonymous([]byte{}, decodedMessage, &publicKey, &privateKey) | ||
if !ok { | ||
err = fmt.Errorf("%w: message could not be decrypted", commonerrors.ErrInvalid) | ||
return | ||
} | ||
decryptedMessage = string(message) | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package encryption | ||
|
||
import ( | ||
"encoding/base64" | ||
"encoding/json" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/bxcodec/faker/v3" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/ARM-software/golang-utils/utils/commonerrors" | ||
"github.com/ARM-software/golang-utils/utils/commonerrors/errortest" | ||
"github.com/ARM-software/golang-utils/utils/logs/logstest" | ||
) | ||
|
||
func TestGenerate(t *testing.T) { | ||
pair, err := GenerateKeyPair() | ||
require.NoError(t, err) | ||
assert.NotEmpty(t, pair.GetPublicKey()) | ||
assert.NotEmpty(t, pair.GetPrivateKey()) | ||
|
||
b, err := base64.StdEncoding.DecodeString(pair.GetPublicKey()) | ||
require.NoError(t, err) | ||
assert.Equal(t, 32, len(b)) | ||
b, err = base64.StdEncoding.DecodeString(pair.GetPrivateKey()) | ||
require.NoError(t, err) | ||
assert.Equal(t, 32, len(b)) | ||
} | ||
|
||
func TestEncryptDecrypt(t *testing.T) { | ||
message := faker.Paragraph() | ||
pair, err := GenerateKeyPair() | ||
require.NoError(t, err) | ||
|
||
encrypted, err := EncryptWithPublicKey(pair.GetPublicKey(), message) | ||
require.NoError(t, err) | ||
decryptedMessage, err := DecryptWithKeyPair(pair, encrypted) | ||
require.NoError(t, err) | ||
assert.Equal(t, message, decryptedMessage) | ||
} | ||
|
||
func TestKeyPrint(t *testing.T) { | ||
// Test to make sure the private key does not get printing in the logs by mistake. | ||
pair, err := GenerateKeyPair() | ||
require.NoError(t, err) | ||
|
||
fmtString := fmt.Sprintf("test: %v", pair) | ||
assert.NotContains(t, fmtString, pair.GetPrivateKey()) | ||
fmt.Println(fmtString) | ||
|
||
fmtString = fmt.Sprintf("test: %+v", pair) | ||
assert.NotContains(t, fmtString, pair.GetPrivateKey()) | ||
fmt.Println(fmtString) | ||
|
||
fmtString = fmt.Sprintf("test: %q", pair) | ||
assert.NotContains(t, fmtString, pair.GetPrivateKey()) | ||
fmt.Println(fmtString) | ||
|
||
fmtJSON, err := json.Marshal(pair) | ||
require.NoError(t, err) | ||
fmtString = string(fmtJSON) | ||
assert.NotContains(t, fmtString, pair.GetPrivateKey()) | ||
fmt.Println(fmtString) | ||
logger := logstest.NewTestLogger(t) | ||
logger.Info("test", "key", pair) | ||
} | ||
|
||
func TestEncryptDecrypt_Failures(t *testing.T) { | ||
pair, err := GenerateKeyPair() | ||
require.NoError(t, err) | ||
|
||
_, err = EncryptWithPublicKey(faker.Name(), faker.Word()) | ||
require.Error(t, err) | ||
errortest.AssertError(t, err, commonerrors.ErrInvalid) | ||
|
||
invalidPair := newBasicKeyPair(faker.Name(), faker.Name()) | ||
_, err = DecryptWithKeyPair(invalidPair, faker.Word()) | ||
require.Error(t, err) | ||
errortest.AssertError(t, err, commonerrors.ErrInvalid) | ||
|
||
invalidPair = newBasicKeyPair(pair.GetPublicKey(), faker.Name()) | ||
_, err = DecryptWithKeyPair(invalidPair, faker.Word()) | ||
require.Error(t, err) | ||
errortest.AssertError(t, err, commonerrors.ErrInvalid) | ||
|
||
invalidPair = newBasicKeyPair(faker.Name(), pair.GetPrivateKey()) | ||
_, err = DecryptWithKeyPair(invalidPair, faker.Word()) | ||
require.Error(t, err) | ||
errortest.AssertError(t, err, commonerrors.ErrInvalid) | ||
|
||
_, err = DecryptWithKeyPair(pair, faker.Word()) | ||
require.Error(t, err) | ||
errortest.AssertError(t, err, commonerrors.ErrInvalid) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Package encryption defines utilities with regards to cryptography. | ||
package encryption | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
) | ||
|
||
//go:generate mockgen -destination=../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/$GOPACKAGE IKeyPair | ||
|
||
// IKeyPair defines an asymmetric key pair for cryptography. | ||
type IKeyPair interface { | ||
fmt.Stringer | ||
fmt.GoStringer | ||
json.Marshaler | ||
// GetPublicKey returns the public key (base64 encoded) | ||
GetPublicKey() string | ||
// GetPrivateKey returns the private key (base64 encoded) | ||
GetPrivateKey() string | ||
} |
Oops, something went wrong.