Created
September 5, 2018 10:54
-
-
Save proteye/e54eef1713e1fe9123d1eb04c0a5cf9b to your computer and use it in GitHub Desktop.
How to AES-256 (CBC/CFB mode) encrypt and decrypt in Dart/Flutter with Pointy Castle
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
import 'dart:convert'; | |
import 'dart:typed_data'; | |
import "package:pointycastle/export.dart"; | |
import "./convert_helper.dart"; | |
// AES key size | |
const KEY_SIZE = 32; // 32 byte key for AES-256 | |
const ITERATION_COUNT = 1000; | |
class AesHelper { | |
static const CBC_MODE = 'CBC'; | |
static const CFB_MODE = 'CFB'; | |
static Uint8List deriveKey(dynamic password, | |
{String salt = '', | |
int iterationCount = ITERATION_COUNT, | |
int derivedKeyLength = KEY_SIZE}) { | |
if (password == null || password.isEmpty) { | |
throw new ArgumentError('password must not be empty'); | |
} | |
if (password is String) { | |
password = createUint8ListFromString(password); | |
} | |
Uint8List saltBytes = createUint8ListFromString(salt); | |
Pbkdf2Parameters params = | |
new Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength); | |
KeyDerivator keyDerivator = | |
new PBKDF2KeyDerivator(new HMac(new SHA256Digest(), 64)); | |
keyDerivator.init(params); | |
return keyDerivator.process(password); | |
} | |
static Uint8List pad(Uint8List src, int blockSize) { | |
var pad = new PKCS7Padding(); | |
pad.init(null); | |
int padLength = blockSize - (src.length % blockSize); | |
var out = new Uint8List(src.length + padLength)..setAll(0, src); | |
pad.addPadding(out, src.length); | |
return out; | |
} | |
static Uint8List unpad(Uint8List src) { | |
var pad = new PKCS7Padding(); | |
pad.init(null); | |
int padLength = pad.padCount(src); | |
int len = src.length - padLength; | |
return new Uint8List(len)..setRange(0, len, src); | |
} | |
static String encrypt(String password, String plaintext, | |
{String mode = CBC_MODE}) { | |
Uint8List derivedKey = deriveKey(password); | |
KeyParameter keyParam = new KeyParameter(derivedKey); | |
BlockCipher aes = new AESFastEngine(); | |
var rnd = FortunaRandom(); | |
rnd.seed(keyParam); | |
Uint8List iv = rnd.nextBytes(aes.blockSize); | |
BlockCipher cipher; | |
ParametersWithIV params = new ParametersWithIV(keyParam, iv); | |
switch (mode) { | |
case CBC_MODE: | |
cipher = new CBCBlockCipher(aes); | |
break; | |
case CFB_MODE: | |
cipher = new CFBBlockCipher(aes, aes.blockSize); | |
break; | |
default: | |
throw new ArgumentError('incorrect value of the "mode" parameter'); | |
break; | |
} | |
cipher.init(true, params); | |
Uint8List textBytes = createUint8ListFromString(plaintext); | |
Uint8List paddedText = pad(textBytes, aes.blockSize); | |
Uint8List cipherBytes = _processBlocks(cipher, paddedText); | |
Uint8List cipherIvBytes = new Uint8List(cipherBytes.length + iv.length) | |
..setAll(0, iv) | |
..setAll(iv.length, cipherBytes); | |
return base64.encode(cipherIvBytes); | |
} | |
static String decrypt(String password, String ciphertext, | |
{String mode = CBC_MODE}) { | |
Uint8List derivedKey = deriveKey(password); | |
KeyParameter keyParam = new KeyParameter(derivedKey); | |
BlockCipher aes = new AESFastEngine(); | |
Uint8List cipherIvBytes = base64.decode(ciphertext); | |
Uint8List iv = new Uint8List(aes.blockSize) | |
..setRange(0, aes.blockSize, cipherIvBytes); | |
BlockCipher cipher; | |
ParametersWithIV params = new ParametersWithIV(keyParam, iv); | |
switch (mode) { | |
case CBC_MODE: | |
cipher = new CBCBlockCipher(aes); | |
break; | |
case CFB_MODE: | |
cipher = new CFBBlockCipher(aes, aes.blockSize); | |
break; | |
default: | |
throw new ArgumentError('incorrect value of the "mode" parameter'); | |
break; | |
} | |
cipher.init(false, params); | |
int cipherLen = cipherIvBytes.length - aes.blockSize; | |
Uint8List cipherBytes = new Uint8List(cipherLen) | |
..setRange(0, cipherLen, cipherIvBytes, aes.blockSize); | |
Uint8List paddedText = _processBlocks(cipher, cipherBytes); | |
Uint8List textBytes = unpad(paddedText); | |
return new String.fromCharCodes(textBytes); | |
} | |
static Uint8List _processBlocks(BlockCipher cipher, Uint8List inp) { | |
var out = new Uint8List(inp.lengthInBytes); | |
for (var offset = 0; offset < inp.lengthInBytes;) { | |
var len = cipher.processBlock(inp, offset, out, offset); | |
offset += len; | |
} | |
return out; | |
} | |
} |
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
import "dart:typed_data"; | |
import 'dart:convert'; | |
import 'package:convert/convert.dart' as convert; | |
Uint8List createUint8ListFromString(String s) { | |
var ret = new Uint8List(s.length); | |
for (var i = 0; i < s.length; i++) { | |
ret[i] = s.codeUnitAt(i); | |
} | |
return ret; | |
} | |
Uint8List createUint8ListFromHexString(String hex) { | |
var result = new Uint8List(hex.length ~/ 2); | |
for (var i = 0; i < hex.length; i += 2) { | |
var num = hex.substring(i, i + 2); | |
var byte = int.parse(num, radix: 16); | |
result[i ~/ 2] = byte; | |
} | |
return result; | |
} | |
Uint8List createUint8ListFromSequentialNumbers(int len) { | |
var ret = new Uint8List(len); | |
for (var i = 0; i < len; i++) { | |
ret[i] = i; | |
} | |
return ret; | |
} | |
String formatBytesAsHexString(Uint8List bytes) { | |
var result = new StringBuffer(); | |
for (var i = 0; i < bytes.lengthInBytes; i++) { | |
var part = bytes[i]; | |
result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}'); | |
} | |
return result.toString(); | |
} | |
List<int> decodePEM(String pem) { | |
var startsWith = [ | |
"-----BEGIN PUBLIC KEY-----", | |
"-----BEGIN PRIVATE KEY-----", | |
"-----BEGIN ENCRYPTED MESSAGE-----", | |
"-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n", | |
"-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n", | |
]; | |
var endsWith = [ | |
"-----END PUBLIC KEY-----", | |
"-----END PRIVATE KEY-----", | |
"-----END ENCRYPTED MESSAGE-----", | |
"-----END PGP PUBLIC KEY BLOCK-----", | |
"-----END PGP PRIVATE KEY BLOCK-----", | |
]; | |
bool isOpenPgp = pem.indexOf('BEGIN PGP') != -1; | |
for (var s in startsWith) { | |
if (pem.startsWith(s)) { | |
pem = pem.substring(s.length); | |
} | |
} | |
for (var s in endsWith) { | |
if (pem.endsWith(s)) { | |
pem = pem.substring(0, pem.length - s.length); | |
} | |
} | |
if (isOpenPgp) { | |
var index = pem.indexOf('\r\n'); | |
pem = pem.substring(0, index); | |
} | |
pem = pem.replaceAll('\n', ''); | |
pem = pem.replaceAll('\r', ''); | |
return base64.decode(pem); | |
} | |
List<int> decodeHex(String hex) { | |
hex = hex | |
.replaceAll(':', '') | |
.replaceAll('\n', '') | |
.replaceAll('\r', '') | |
.replaceAll('\t', ''); | |
return convert.hex.decode(hex); | |
} |
Hellow
Why use it
KeyDerivator keyDerivator =
new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
The decrypted data is preceded by IV
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi,
I wanted to use this for my own mobile project and It found that the ITERATION_COUNT is too large in the mobile operation and seems to block execution time(for like at least like1-2 sec) even when used with future async. So, I reduced it to 250 which didn't block the execution that long.
My question is, is it alright to reduce the ITERATION_COUNT?