Skip to content

Commit

Permalink
feat: Add encrypt-data CLI (#529)
Browse files Browse the repository at this point in the history
Co-authored-by: gyuguen <gyuguen.jang@medibloc.org>
  • Loading branch information
0xHansLee and gyuguen authored Dec 12, 2022
1 parent eb408fc commit 82c9400
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 0 deletions.
108 changes: 108 additions & 0 deletions cmd/panacead/cmd/encrypt_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package cmd

import (
"encoding/base64"
"encoding/hex"
"os"

"github.com/btcsuite/btcd/btcec"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/medibloc/panacea-core/v2/crypto"
oracletypes "github.com/medibloc/panacea-core/v2/x/oracle/types"
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/libs/cli"
)

func EncryptDataCmd(defaultNodeHome string) *cobra.Command {
cmd := &cobra.Command{
Use: "encrypt-data [input-file-path] [output-file-path] [key-name]",
Short: "Encrypt data with shared key which consists of oracle public key and provider's private key",
Long: `
This command can encrypt data with shared key which consists of oracle public key and provider's private key.
The key to be used for encryption should be stored in the localStore.
If not stored, please add the key first via the following command.
panacead keys add ...
`,
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

queryClient := oracletypes.NewQueryClient(clientCtx)

origData, err := os.ReadFile(args[0])
if err != nil {
return err
}

params, err := queryClient.Params(cmd.Context(), &oracletypes.QueryOracleParamsRequest{})
if err != nil {
return err
}

oraclePubKey := params.GetParams().GetOraclePublicKey()

encryptedData, err := encrypt(clientCtx, args[2], origData, oraclePubKey)
if err != nil {
return err
}

if err := os.WriteFile(args[1], encryptedData, 0644); err != nil {
return err
}

return nil
},
}

cmd.PersistentFlags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
cmd.PersistentFlags().String(flags.FlagKeyringDir, "", "The client Keyring directory; if omitted, the default 'home' directory will be used")
cmd.PersistentFlags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
cmd.PersistentFlags().String(cli.OutputFlag, "text", "Output format (text|json)")
cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID")

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func encrypt(clientCtx client.Context, keyName string, origData []byte, oraclePubKeyStr string) ([]byte, error) {
// get unsafe export private key from keystore
privKeyHex, err := keyring.NewUnsafe(clientCtx.Keyring).UnsafeExportPrivKeyHex(keyName)
if err != nil {
return nil, err
}

privKeyBz, err := hex.DecodeString(privKeyHex)
if err != nil {
return nil, err
}

privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBz)

// oracle public key
oraclePubKeyBz, err := base64.StdEncoding.DecodeString(oraclePubKeyStr)
if err != nil {
return nil, err
}

oraclePubKey, err := btcec.ParsePubKey(oraclePubKeyBz, btcec.S256())
if err != nil {
return nil, err
}

// shared key
sharedKey := crypto.DeriveSharedKey(privKey, oraclePubKey, crypto.KDFSHA256)

// encrypt data
encryptedData, err := crypto.Encrypt(sharedKey, nil, origData)
if err != nil {
return nil, err
}

return encryptedData, nil
}
1 change: 1 addition & 0 deletions cmd/panacead/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) {
debug.Cmd(),
// this line is used by starport scaffolding # stargate/root/commands
AddGenesisWasmMsgCmd(app.DefaultNodeHome),
EncryptDataCmd(app.DefaultNodeHome),
)

a := appCreator{encodingConfig}
Expand Down
83 changes: 83 additions & 0 deletions crypto/aes256.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package crypto

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"fmt"
"io"

"github.com/btcsuite/btcd/btcec"
)

// DeriveSharedKey derives a shared key (which can be used for asymmetric encryption)
// using a specified KDF (Key Derivation Function)
// from a shared secret generated by Diffie-Hellman key exchange (ECDH).
func DeriveSharedKey(priv *btcec.PrivateKey, pub *btcec.PublicKey, kdf func([]byte) []byte) []byte {
sharedSecret := btcec.GenerateSharedSecret(priv, pub)
return kdf(sharedSecret)
}

// KDFSHA256 is a key derivation function which uses SHA256.
func KDFSHA256(in []byte) []byte {
out := sha256.Sum256(in)
return out[:]
}

// Encrypt combines secretKey and secondKey to encrypt with AES256-GCM method.
func Encrypt(secretKey, additional, data []byte) ([]byte, error) {
if len(secretKey) != 32 {
return nil, fmt.Errorf("secret key is not for AES-256: total %d bits", 8*len(secretKey))
}

// prepare AES-256-GSM cipher
block, err := aes.NewCipher(secretKey)
if err != nil {
return nil, err
}

aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

// make random nonce
nonce := make([]byte, aesGCM.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}

// encrypt data with second key
ciphertext := aesGCM.Seal(nonce, nonce, data, additional)
return ciphertext, nil
}

// Decrypt combines secretKey and secondKey to decrypt AES256-GCM.
func Decrypt(secretKey []byte, additional []byte, ciphertext []byte) ([]byte, error) {
if len(secretKey) != 32 {
return nil, fmt.Errorf("secret key is not for AES-256: total %d bits", 8*len(secretKey))
}

// prepare AES-256-GCM cipher
block, err := aes.NewCipher(secretKey)
if err != nil {
return nil, err
}

aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

nonceSize := aesgcm.NonceSize()
nonce, pureCiphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]

// decrypt ciphertext with second key
plaintext, err := aesgcm.Open(nil, nonce, pureCiphertext, additional)
if err != nil {
return nil, err
}

return plaintext, nil
}
34 changes: 34 additions & 0 deletions crypto/aes256_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package crypto

import (
"crypto/rand"
"io"
"testing"

"github.com/btcsuite/btcd/btcec"
"github.com/stretchr/testify/require"
)

func TestDecryptWithAES256(t *testing.T) {
privKey1, err := btcec.NewPrivateKey(btcec.S256())
require.NoError(t, err)
privKey2, err := btcec.NewPrivateKey(btcec.S256())
require.NoError(t, err)

data := []byte("hello, Panacea")

shareKey1 := DeriveSharedKey(privKey1, privKey2.PubKey(), KDFSHA256)
shareKey2 := DeriveSharedKey(privKey2, privKey1.PubKey(), KDFSHA256)

nonce := make([]byte, 12)
_, err = io.ReadFull(rand.Reader, nonce)
require.NoError(t, err)

encryptedData, err := Encrypt(shareKey1, nonce, data)
require.NoError(t, err)

decryptedData, err := Decrypt(shareKey2, nonce, encryptedData)
require.NoError(t, err)

require.Equal(t, decryptedData, data)
}

0 comments on commit 82c9400

Please sign in to comment.