How To Do Symmetric Key Encryption on a Blockchain

--

Solidity does not natively support symmetric key encryption, such as with AES. Overall, AES would consume too much gas and thus be expensive to implement. But, we can use hashing and X-OR methods to do the encryption. In this case, we will create a 256-bit random value, and then encrypt it with an encryption key, and then decrypt it. In this example, we will take a secret share (sij) and encrypt it with a key (kij) and then allow the smart contract to decrypt it. The example is based on a distributed key generation system using smart contracts [1]:

With symmetric-key, we have the ability to encrypt with a key and then decrypt with the same key. The most typical method of this is AES. But, AES is a power-intensive method which can take up quite a bit of memory. It is thus not well matched to Ethereum, and which will charge gas for the processing. Well, in our toolbox is the Keccak-256 hashing method, which adds points, multiplies points, and uses XOR methods. So, let’s see if we can create a symmetric key method using just Keccak-256 and XOR.

For this, we will take a secret (s_ij) and use an encryption key (k_ij) to encrypt the value, and then for us to be able to reverse this back to the secret. Each of the values will be 256 bits long (as this supports uint256). First, we hash our secret key and append a counter value (j):

The encrypted value is then:

and then to decrypt:

The hash method will just be Keccak-256. In our smart contract, when we want to generate the decryption key (decryption_key), we can generate with:

uint256 decryption_key = uint256(keccak256(abi.encodePacked(sij, j)));

and then decrypt with:

sij ^= decryption_key;

Note that the “^” operation is XOR in Solidity. The following is the code [taken from here][1][code]:

import secrets
import web3
from typing import Tuple, Dict, List, Iterable, Union
from py_ecc.optimized_bn128 import G1, G2
from py_ecc.optimized_bn128 import add, multiply, neg, normalize
from py_ecc.optimized_bn128 import curve_order as CURVE_ORDER
from py_ecc.optimized_bn128 import field_modulus as FIELD_MODULUS
from py_ecc.typing import Optimized_Point3D
from py_ecc.fields import optimized_bn128_FQ, optimized_bn128_FQ2
PointG1 = Optimized_Point3D[optimized_bn128_FQ]
keccak_256 = web3.Web3.solidityKeccak

def random_scalar() -> int:
return secrets.randbelow(CURVE_ORDER)

def encrypt_share(s_ij: int, k_ij: PointG1, j: int) -> int:
x = normalize(k_ij)[0].n
h = keccak_256(abi_types=["uint256", "uint256"], values=[x, j])
return s_ij ^ int.from_bytes(h, "big")

sij=random_scalar()
sk = random_scalar()
kij = multiply(G1, sk)
s_bar = encrypt_share(sij,kij,1)
s_recovered = encrypt_share(s_bar,kij,1)
print(f"sij (random):\t{sij}")
print(f"\nkij (random):\t{kij}")
print(f"\ns_bar (encrypted):\t{s_bar}")
print(f"\nsij (recovered):\t{s_recovered}")

A sample run gives [here]:

sij (random): 15406876896539067097140597458487302616553123769610477281034868610016143252395

kij (random): (5783791198709247499244376033878644472809903156605669489657578769719291144220, 3427976152664943344068288169770639841914517906530964549557248133869518630348, 5153890840379491196634762386417574579639576402954864792138310040630436694547)

s_bar (encrypted): 36505052980076577917894185444826629327445079686761253444579044360821205380626

sij (recovered): 15406876896539067097140597458487302616553123769610477281034868610016143252395

In the smart contract we can decrypt our shares with [here]:

uint256 decryption_key = uint256(keccak256(abi.encodePacked(sij, j)));
sij ^= decryption_key;

References

[1] Schindler, P., Judmayer, A., Stifter, N., & Weippl, E. (2019). Ethdkg: Distributed key generation with ethereum smart contracts. Cryptology ePrint Archive.

--

--

Prof Bill Buchanan OBE FRSE
ASecuritySite: When Bob Met Alice

Professor of Cryptography. Serial innovator. Believer in fairness, justice & freedom. Based in Edinburgh. Old World Breaker. New World Creator. Building trust.