devblockchain

How to Build an End-to-End encryption in Hyperledger Fabric

Blockchain goes hand in hand with cryptography, so it is important that we maintain security and privacy of data. Extending the principles of blockchain, learn how to implement end-to-end encryption in smart contracts for the Fabric framework.

Karthik Kamalakannan

Karthik Kamalakannan

How to Build an End-to-End encryption in Hyperledger Fabric

Hyperledger Fabric has unlimited potential, and is one of the best frameworks to implement your blockchain business network in. The current version, Fabric 1.1, involves the use of CouchDB or LevelDB as storage/state/database options.

Although the network implementation considers privacy and security of data in the network as a priority, by default, all of the data that is stored in the state is plain-text. When we deal with critical data, it is always considered the best practice to not store as plain-text. It's in the nature of blockchains to contain cryptographic signing and encryption data.

This article will explain how to encrypt and then store the data to the state, and obviously how to decrypt and use it, all in a secure manner.

Assumptions

You know how to write and deploy Go chaincode to the network, and are comfortable working with Hyperledger Fabric.

If you don't, read through our previous articles to get settled.

Understanding Encryption Resources

Before we dive into the implementation, let's understand what resources Fabric provides to implement this, and what their role is. If you want to get directly to the implementation, skip this section.

The packages that we'll be using include,

  • BCCSP - The BlockChain Cryptographic Service Provider is the package that offers the implementation of cryptographic standards and algorithms.
  • BCCSPFactory - The BCCSPFactory is used to get instances of the BCCSP interface. A Factory, has a name used to address it.
  • Entities - An entity is the basic interface for all crypto entities that are used by the library to obtain chaincode-level encryption. This package holds many methods that implement cryptographic operations. We will be using 3 such methods, one for creating the AES256 entity, and the other two to perform the encryption and decryption.

Getting started

We will use the basic-network configuration provided in Hyperledger Fabric's documentation, as our template. We will make use of an isolated package, making it easier to use in multiple projects. So configure your network according to the documentation.

Creating the package

Let's create a go package with a cool name, FabCrypt, to hold the encryption and decryption middleware.

The directory structure as follows,

- chaincode
-- fabcrypt
----- wrapper.go
----- utils.go
--- main.go

BCCSP provides two primary functions that we'll use here

  • Simple Encryption and Decryption
  • Signed Encryption and Decryption

We will use and implement the former for this article.

Writing Chaincode

  • We start by creating the primary functions, that do the main work
    • Encrypt and Store to state
    • Get from state and Decrypt

utils.go

package fabcrypt
 
import (
        "encoding/json"
 
        "github.com/hyperledger/fabric/core/chaincode/shim"
        "github.com/hyperledger/fabric/core/chaincode/shim/ext/entities"
        "github.com/pkg/errors"
)
 
// getStateAndDecrypt retrieves the value associated to key,
// decrypts it with the supplied entity and returns the result
// of the decryption
func getStateAndDecrypt(stub shim.ChaincodeStubInterface, ent entities.Encrypter, key string) ([]byte, error) {
        // at first we retrieve the ciphertext from the ledger
        ciphertext, err := stub.GetState(key)
        if err != nil {
                return nil, err
        }
 
        if len(ciphertext) == 0 {
                return nil, errors.New("no ciphertext to decrypt")
        }
 
        return ent.Decrypt(ciphertext)
}
 
// encryptAndPutState encrypts the supplied value using the
// supplied entity and puts it to the ledger associated to
// the supplied KVS key
func encryptAndPutState(stub shim.ChaincodeStubInterface, ent entities.Encrypter, key string, value []byte) error {
        // at first we use the supplied entity to encrypt the value
        ciphertext, err := ent.Encrypt(value)
        if err != nil {
                return err
        }
 
        return stub.PutState(key, ciphertext)
}
  • Let's create wrappers for these functions, that validate the requirements. The key that will be used to encrypt/decrypt will have to be passed as a 32 byte array (AES 256 bit array). For the article, we will use a 0 filled byte array. Highly recommend to not use this as-is, please add your own key.

wrapper.go

package fabcrypt
 
import (
        "crypto/md5"
        "encoding/hex"
        // "fmt"
        "github.com/hyperledger/fabric/bccsp"
        "github.com/hyperledger/fabric/bccsp/factory"
        "github.com/hyperledger/fabric/core/chaincode/shim"
        "github.com/hyperledger/fabric/core/chaincode/shim/ext/entities"
)
 
// EncCC example simple Chaincode implementation of a chaincode that uses encryption/signatures
type EncCC struct {
        bccspInst bccsp.BCCSP
}
 
// Encrypter encrypts the data and writes to the ledger
func Encrypter(stub shim.ChaincodeStubInterface, key string, valueAsBytes []byte) error {
        factory.InitFactories(nil)
        encCC := EncCC{factory.GetDefault()}
        encKey := make([]byte, 32)
        iv := make([]byte, 16)
        ent, err := entities.NewAES256EncrypterEntity("ID", encCC.bccspInst, encKey, iv)
        if err != nil {
                return err
        }
 
        return encryptAndPutState(stub, ent, key, valueAsBytes)
}
 
// Decrypter decrypts the data and writes to the ledger
func Decrypter(stub shim.ChaincodeStubInterface, key string) ([]byte, error) {
        factory.InitFactories(nil)
        encCC := EncCC{factory.GetDefault()}
        // fmt.Println(encCC)
        decKey := make([]byte, 32)
        iv := make([]byte, 16)
        ent, err := entities.NewAES256EncrypterEntity("ID", encCC.bccspInst, decKey, iv)
        if err != nil {
                return nil, err
        }
 
        return getStateAndDecrypt(stub, ent, key)
}
 
func GetMD5Hash(text string) string {
        hasher := md5.New()
        hasher.Write([]byte(text))
        return hex.EncodeToString(hasher.Sum(nil))
}

Both of these functions specified above are out wrappers, and send in the entity that has been loaded with either the encryption/decryption key, the bccsp instance, and the IV(optional).

In our main package, we will write the testing functions to verify these modules. You can use the implementation as a simple template, to add to the next big thing you will be building.

main.go

package main
 
import (
        "fmt"
 
        eh "github.com/chaincode/errorhandler"
        fc "github.com/chaincode/fabcrypt"
        "github.com/hyperledger/fabric/core/chaincode/lib/cid"
        "github.com/hyperledger/fabric/core/chaincode/shim"
        sc "github.com/hyperledger/fabric/protos/peer")
 
type SmartContract struct {
}
 
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
        return shim.Success(nil)
}
 
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
 
        function, args := APIstub.GetFunctionAndParameters()
 
        if function == "testEncrypt" {
                return s.testEncrypt(APIstub)
        } else if function == "testDecrypt" {
                return s.testDecrypt(APIstub)
        }
        return shim.Error("Invalid Smart Contract function name.")
}
 
func (s *SmartContract) testEncrypt(APIstub shim.ChaincodeStubInterface) sc.Response {
        value := "WakandaForeva"
        key := "testId"
        err := fc.Encrypter(APIstub, key, []byte(value))
        if err != nil {
                return shim.Error(err.Error())
        }
        return shim.Success(nil)
}
 
func (s *SmartContract) testDecrypt(APIstub shim.ChaincodeStubInterface) sc.Response {
        key := "testId"
        val, err := fc.Decrypter(APIstub, key)
        if err != nil {
                return shim.Error(err.Error())
        }
        return shim.Success(val)
}
func main() {
        err := shim.Start(new(SmartContract))
        if err != nil {
                fmt.Printf("Error starting SmartContract chaincode: %s", err)
        }
}

Testing The Chaincode

Let's test our chaincode with the network. Make sure that you have the cli container up and running. The flag -C specifies the channel name, -n specifies the chaincode name, -c specify the inputs we're sending.

As you can see from the above snippet, "WakandaForeva" is the string we're trying to encrypt.

docker exec -it cli peer chaincode invoke -C mychannel -n mycc -c '{"Args" : ["testEncrypt"]}'

You can navigate to http://localhost:5984/_utils/ in your browser, to verify CouchDB's interface. This is how the record that got stored should look like.

You will not be able to see the value "WakandaForeva" anywhere here, and notice that it has been encrypted.

Let's test the decryption now.

docker exec -it cli peer chaincode query -C mychannel -n mycc -c '{"Args" : ["testDecrypt"]}'

On executing this command, we get the following response -

It worked! The query result is "WakandaForeva"

So this is how we can implement end to end encryption for our Hyperledger Fabric chaincode. Comment below if you have any questions or suggestions!

Last updated: January 23rd, 2024 at 1:50:36 PM GMT+0

Subscribe

Get notified about new updates on Product Management, Building SaaS, and more.

Skcript 10th Anniversary

Consultants for ambitious businesses.

Copyright © 2024 Skcript Technologies Pvt. Ltd.