Skip to content

Instantly share code, notes, and snippets.

@willshiao
Last active January 19, 2024 16:18
Show Gist options
  • Save willshiao/f4b03650e5a82561a460b4a15789cfa1 to your computer and use it in GitHub Desktop.
Save willshiao/f4b03650e5a82561a460b4a15789cfa1 to your computer and use it in GitHub Desktop.
AES 256-CFB in Node.js, Go, and Python
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
)
func keyEncrypt(keyStr string, cryptoText string) string {
keyBytes := sha256.Sum256([]byte(keyStr))
return encrypt(keyBytes[:], cryptoText)
}
// encrypt string to base64 crypto using AES
func encrypt(key []byte, text string) string {
plaintext := []byte(text)
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
return base64.StdEncoding.EncodeToString(ciphertext)
}
func keyDecrypt(keyStr string, cryptoText string) string {
keyBytes := sha256.Sum256([]byte(keyStr))
return decrypt(keyBytes[:], cryptoText)
}
// decrypt from base64 to decrypted string
func decrypt(key []byte, cryptoText string) string {
ciphertext, err := base64.StdEncoding.DecodeString(cryptoText)
if err != nil {
panic(err)
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
if len(ciphertext) < aes.BlockSize {
panic("ciphertext too short")
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
// XORKeyStream can work in-place if the two arguments are the same.
stream.XORKeyStream(ciphertext, ciphertext)
return fmt.Sprintf("%s", ciphertext)
}
func main() {
encrypted := "lIR3JIHpomC5Zm8sjy29D/xFcXUX0c/4vQ=="
fmt.Println(encrypted)
fmt.Println(keyDecrypt("SecretKey", encrypted))
}
'use strict';
const crypto = require('crypto');
const algorithm = 'aes-256-cfb';
function encryptText(keyStr, text) {
const hash = crypto.createHash('sha256');
hash.update(keyStr);
const keyBytes = hash.digest();
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, keyBytes, iv);
console.log('IV:', iv);
let enc = [iv, cipher.update(text, 'utf8')];
enc.push(cipher.final());
return Buffer.concat(enc).toString('base64');
}
function decryptText(keyStr, text) {
const hash = crypto.createHash('sha256');
hash.update(keyStr);
const keyBytes = hash.digest();
const contents = Buffer.from(text, 'base64');
const iv = contents.slice(0, 16);
const textBytes = contents.slice(16);
const decipher = crypto.createDecipheriv(algorithm, keyBytes, iv);
let res = decipher.update(textBytes, '', 'utf8');
res += decipher.final('utf8');
return res;
}
const encrypted = encryptText('SecretKey', 'It works!');
console.log('Encrypted: ', encrypted);
const decrypted = decryptText('SecretKey', encrypted);
console.log('Decrypted: ', decrypted);
import base64
import hashlib
from Crypto.Cipher import AES
from Crypto import Random
def encrypt(keyStr, text):
private_key = hashlib.sha256(keyStr.encode()).digest()
rem = len(text) % 16
padded = str.encode(text) + (b'\0' * (16 - rem)) if rem > 0 else str.encode(text)
iv = Random.new().read(AES.block_size)
cipher = AES.new(private_key, AES.MODE_CFB, iv, segment_size=128)
enc = cipher.encrypt(padded)[:len(text)]
return base64.b64encode(iv + enc).decode()
def decrypt(keyStr, text):
private_key = hashlib.sha256(keyStr.encode()).digest()
text = base64.b64decode(text)
iv, value = text[:16], text[16:]
rem = len(value) % 16
padded = value + (b'\0' * (16 - rem)) if rem > 0 else value
cipher = AES.new(private_key, AES.MODE_CFB, iv, segment_size=128)
return (cipher.decrypt(padded)[:len(value)]).decode()
def main():
encrypted = 'lIR3JIHpomC5Zm8sjy29D/xFcXUX0c/4vQ=='
print(encrypted)
print(decrypt('SecretKey', encrypted))
if __name__== '__main__':
main()
@gauravmehla
Copy link

Hey, Thanks a lot for this. Can we have a solution for python also. What i need is a way of encryption which I can decrypt using python, go and node all.

@paracha3
Copy link

paracha3 commented Jul 5, 2019

Hey, Thanks a lot for this. Can we have a solution for python also. What i need is a way of encryption which I can decrypt using python, go and node all.

+1. It would be great to have for all languages so you can use in many different apps

@willshiao
Copy link
Author

Hey, Thanks a lot for this. Can we have a solution for python also. What i need is a way of encryption which I can decrypt using python, go and node all.

Added a compatible Python implementation.

@amaurymercier
Copy link

Line 10 of aes.py should be padded = str.encode(text) + (b'\0' * (16 - rem)) if rem > 0 else str.encode(text) .

@willshiao
Copy link
Author

Line 10 of aes.py should be padded = str.encode(text) + (b'\0' * (16 - rem)) if rem > 0 else str.encode(text) .

You are correct. I just edited the implementation above to include this change.

Copy link

ghost commented Jun 29, 2020

Thanks a lot for this.

@Muzasarali
Copy link

Muzasarali commented May 17, 2021

Thank a lot for this program 😇😇

@gBusato
Copy link

gBusato commented Feb 11, 2022

Thanks for this, you seems to be the only one knowing what you are doing on internet about crypting stuff

@iperera97
Copy link

iperera97 commented Feb 13, 2022

but if we decrypt with invalid cipher in nodejs return look like this value. ,{!�ߺ`�KQ��mh�
So how do I make sure it's a invalid cipher text ? because both success and failed scenarios return a string

@Bryce-huang
Copy link

Bryce-huang commented Mar 8, 2022

with JAVA
i use the lib : implementation('commons-codec:commons-codec:1.15')
other,You can change hex encoding to base64 encoding

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;

public class AesUtil {
	static String key = "SecretKey";

	private static byte[] decrypt(byte[] payload, byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {

		SecretKeySpec key_spec = new SecretKeySpec(key, "AES");
		Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
		int block_size = cipher.getBlockSize();
		IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(payload, block_size));
		byte[] decryption_data = Arrays.copyOfRange(payload, block_size, payload.length);
		cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
		return cipher.doFinal(decryption_data);
	}

	public static String decrypt(String payload) {
		try {
			byte[] decrypt = new byte[0];
			try {
				decrypt = decrypt(Hex.decodeHex(payload), sha256(key));
			} catch (DecoderException e) {
				e.printStackTrace();
			}
			return new String(decrypt);
		} catch (NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException | InvalidKeyException e) {
			e.printStackTrace();
		}
		return null;
	}

	private static byte[] encrypt(byte[] encoded_payload, byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, ShortBufferException, IllegalBlockSizeException, BadPaddingException, ShortBufferException {
		SecureRandom rand = new SecureRandom();


		SecretKeySpec key_spec = new SecretKeySpec(key, "AES");
		Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");

		int block_size = cipher.getBlockSize();

		byte[] buffer = new byte[block_size];
		rand.nextBytes(buffer);
		IvParameterSpec iv = new IvParameterSpec(buffer);
		buffer = Arrays.copyOf(buffer, block_size + encoded_payload.length);
		cipher.init(Cipher.ENCRYPT_MODE, key_spec, iv);
		cipher.doFinal(encoded_payload, 0, encoded_payload.length, buffer, block_size);
		return buffer;
	}

	public static String encrypt(String text) {
		try {
			byte[] encrypted = encrypt(text.getBytes(StandardCharsets.UTF_8), sha256(key));
			return Hex.encodeHexString(encrypted);
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (InvalidAlgorithmParameterException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (ShortBufferException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static void main(String[] args) {
		//String encrypted = encrypt("123345");
		String decrypt = decrypt("25ae775850d949f1d6b1161180d23c10e4f5646e2635b8d7cd");
		System.out.println(decrypt);
	}

	public static byte[] sha256(String value) {
		if (value == null || value.length() == 0) {
			return null;
		}
		MessageDigest messageDigest = null;
		try {
			messageDigest = MessageDigest.getInstance("sha-256");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		byte[] buffer = value.getBytes(StandardCharsets.UTF_8);
		buffer = messageDigest.digest(buffer);
		return buffer;
	}
}

@takatsugu-kato
Copy link

takatsugu-kato commented Jul 14, 2022

Thanks a lot! I had to do both encryption and decryption with js and python, and this code was very helpful.
Only one point needed to be fixed in the py code.

enc = cipher.encrypt(padded)[:len(text)]
change to
enc = cipher.encrypt(padded)[:len(str.encode(text)]

By changing this, multi-byte characters can be properly encrypted and decrypted.

@timeturnback
Copy link

this is how to do it with crypto-js . Very painful for me to find it our

import CryptoJS from 'crypto-js';

export function encryptText(keyStr, text) {
  const private_key = CryptoJS.SHA256(keyStr).toString(CryptoJS.enc.Latin1);
  const rem = text.length % 16;
  const padded = CryptoJS.enc.Latin1.parse(text.padEnd(text.length + (16 - rem), '\0'));
  const iv = CryptoJS.lib.WordArray.random(16);
  const cipher = CryptoJS.AES.encrypt(padded, CryptoJS.enc.Latin1.parse(private_key), {
    iv: iv,
    mode: CryptoJS.mode.CFB,
    padding: CryptoJS.pad.NoPadding,
    segmentSize: 128,
  });
  const ciphertext = iv.concat(cipher.ciphertext);
  return ciphertext.toString(CryptoJS.enc.Base64);
}

export const decryptText = (keyStr, text) => {
  const private_key = CryptoJS.SHA256(keyStr).toString(CryptoJS.enc.Latin1);
  const encrypted = CryptoJS.enc.Base64.parse(text);
  const iv = encrypted.clone().words.slice(0, 4);
  const ciphertext = encrypted.clone().words.slice(4);
  const cipherParams = {
    ciphertext: CryptoJS.lib.WordArray.create(ciphertext),
  };
  const decrypted = CryptoJS.AES.decrypt(cipherParams, CryptoJS.enc.Latin1.parse(private_key), {
    iv: CryptoJS.lib.WordArray.create(iv),
    mode: CryptoJS.mode.CFB,
    padding: CryptoJS.pad.ZeroPadding,
    segmentSize: 128,
  });
  const decryptedText = CryptoJS.enc.Utf8.stringify(decrypted);
  return decryptedText;
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment