Skip to content

Instantly share code, notes, and snippets.

@auxten
Last active May 22, 2019 08:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save auxten/451eebc568007de9552f0431ca6a80a6 to your computer and use it in GitHub Desktop.
Save auxten/451eebc568007de9552f0431ca6a80a6 to your computer and use it in GitHub Desktop.
AES-CBC-PKCS#5 (PKCS#7) with KDF and salt implementation and tests in Java, Golang, Python, JavaScript from github.com/CovenantSQL
'use strict';
var test = require('ava'),
aes = require('aes-js'),
e2e = require('..');
function from_hex(s) {
return new Uint8Array(aes.utils.hex.toBytes(s));
}
test('test golang decryption cases', function(t) {
// following tests are migrated from end-to-end encryption golang implementation
const cases = [
{
raw: '11',
pass: ';#K]As9C*6L',
possibleEncrypted: 'a372ea2c158a2f99d386e309db4355a659a7a8dd3986fd1d94f7604256061609',
},
{
raw: '111282C128421286712857128C2128EF' +
'128B7671283C128571287512830128EC' +
'128391281A1312849128381281E1286A' +
'12871128621287A9D12857128C412886' +
'128FD12834128DA128F5',
pass: '',
possibleEncrypted: '1bfb6a7fda3e3eb1e14c9afd0baefe86' +
'c90979101f179db7e48a0fa7617881e8' +
'f752c59fb512bb86b8ed69c5644bf2dc' +
'30fbcd3bf79fb20342595c84fad00e46' +
'2fab3e51266492a3d5d085e650c1e619' +
'6278d7f5185c263440ec6fd940ffbb85',
},
{
raw: '11',
pass: '\'K]"#\'pi/1/JD2',
possibleEncrypted: 'a83d152777ce3a1c0710b03676ae867c86ab0a47b3ca080f825683ac1079eb41',
},
{
raw: '11111111111111111111111111111111',
pass: '',
possibleEncrypted: '7dda438c4256a63c62d6816617fcbf9c' +
'7773b9b4f87902b7253848ba2b0ed0ba' +
'f70a3ac976a835b7bc3008e9ba43da74',
},
{
raw: '11111111111111111111111111111111',
pass: 'youofdas1312',
possibleEncrypted: 'cab07967cf377dbc010fbf5f84d12bcb' +
'6f8b188e6965738cf9007a671b4bfeb9' +
'f52257aac3808048c341dcaa1c125ca7',
},
{
raw: '11111111111111111111111111',
pass: '空のBottle😄',
possibleEncrypted: '4384874473945c5b70519ad5ace6305ef6b78c60c3c694add08a8b81899c4171',
},
];
cases.forEach(function(c) {
const d = e2e.decrypt(from_hex(c.possibleEncrypted), c.pass);
t.deepEqual(d, from_hex(c.raw));
});
});
/*
* Copyright 2019 The CovenantSQL Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except raw compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to raw writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package toolkit
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"github.com/CovenantSQL/CovenantSQL/crypto"
"github.com/CovenantSQL/CovenantSQL/crypto/symmetric"
)
var salt = [...]byte{
0x3f, 0xb8, 0x87, 0x7d, 0x37, 0xfd, 0xc0, 0x4e,
0x4a, 0x47, 0x65, 0xEF, 0xb8, 0xab, 0x7d, 0x36,
}
// Encrypt encrypts data with given password by AES-128-CBC PKCS#7, iv will be placed
// at head of cipher data.
func Encrypt(in, password []byte) (out []byte, err error) {
// keyE will be 128 bits, so aes.NewCipher(keyE) will return
// AES-128 Cipher.
keyE := symmetric.KeyDerivation(password, salt[:])[:16]
paddedIn := crypto.AddPKCSPadding(in)
// IV + padded cipher data
out = make([]byte, aes.BlockSize+len(paddedIn))
// as IV length must equal block size, iv length should be 128 bits
iv := out[:aes.BlockSize]
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
// start encryption, as keyE and iv are generated properly, there should
// not be any error
block, _ := aes.NewCipher(keyE)
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(out[aes.BlockSize:], paddedIn)
return out, nil
}
// Decrypt decrypts data with given password by AES-128-CBC PKCS#7. iv will be read from
// the head of raw.
func Decrypt(in, password []byte) (out []byte, err error) {
keyE := symmetric.KeyDerivation(password, salt[:])[:16]
// IV + padded cipher data == (n + 1 + 1) * aes.BlockSize
if len(in)%aes.BlockSize != 0 || len(in)/aes.BlockSize < 2 {
return nil, errors.New("cipher data size not match")
}
// read IV
iv := in[:aes.BlockSize]
// start decryption, as keyE and iv are generated properly, there should
// not be any error
block, _ := aes.NewCipher(keyE)
mode := cipher.NewCBCDecrypter(block, iv)
// same length as cipher data
plainData := make([]byte, len(in)-aes.BlockSize)
mode.CryptBlocks(plainData, in[aes.BlockSize:])
return crypto.RemovePKCSPadding(plainData)
}
/* global exports define */
(function(root){
'use strict';
const BLOCK_SIZE = 16;
var pkcs7 = require('pkcs7'),
aes = require('aes-js'),
hash = require('hash.js'),
salt = new Uint8Array([
0x3f, 0xb8, 0x87, 0x7d, 0x37, 0xfd, 0xc0, 0x4e,
0x4a, 0x47, 0x65, 0xEF, 0xb8, 0xab, 0x7d, 0x36,
]);
function str_to_buf(s) {
if (typeof(s) == 'string') {
const len = s.length;
const buf = new Uint8Array(len);
for (let i = 0; i != len; ++i) {
buf[i] = s.charCodeAt(i);
}
return buf;
} else {
return s;
}
}
function key_derivation(passwd) {
const h = hash.sha256().update(hash.sha256().update(passwd).update(salt).digest()).digest();
return new Uint8Array(h.slice(0, BLOCK_SIZE));
}
function rand_bytes(sz) {
const iv = new Uint8Array(sz);
for (let i = 0; i != sz; ++i) {
iv[i] = Math.floor(Math.random() * 256);
}
return iv;
}
function pad(s) {
return pkcs7.pad(str_to_buf(s));
}
function unpad(s) {
if (!s || s.length < BLOCK_SIZE) {
throw new Error('invalid pkcs#7 data');
}
const p = s[s.length - 1];
if (p > BLOCK_SIZE) {
throw new Error('invalid pkcs#7 padding length');
}
return pkcs7.unpad(s);
}
function encrypt(s, passwd) {
const p = key_derivation(passwd);
const iv = rand_bytes(BLOCK_SIZE);
const cbc = new aes.ModeOfOperation.cbc(p, iv);
const pad_in = pad(s);
const ret = new Uint8Array(iv.length + pad_in.length);
const ed = cbc.encrypt(pad_in);
if (!ed || !ed.length) {
throw new Error('encrypt failed');
}
ret.set(iv);
ret.set(ed, iv.length);
return ret;
}
function decrypt(c, passwd) {
const p = key_derivation(passwd);
const cb = str_to_buf(c);
if (cb.length < 2 * BLOCK_SIZE) {
throw new Error('invalid cipher');
}
const iv = cb.slice(0, BLOCK_SIZE);
const cbc = new aes.ModeOfOperation.cbc(p, iv);
const dd = cbc.decrypt(cb.slice(BLOCK_SIZE));
if (!dd || !dd.length || dd.length != (cb.length - BLOCK_SIZE)) {
throw new Error('invalid decrypted data');
}
return unpad(new Uint8Array(dd));
}
function decrypt_string(c, passwd) {
const d = decrypt(c, passwd);
return aes.utils.utf8.fromBytes(d);
}
var e2e = {
encrypt: encrypt,
decrypt: decrypt,
decrypt_string: decrypt_string,
};
if (typeof(exports) !== 'undefined') {
module.exports = e2e;
} else if (typeof(define) === 'function' && define.amd) {
define([], function() {
return e2e;
});
} else {
if (root.e2e) {
e2e._e2e = root.e2e;
}
root.e2e = e2e;
}
})(this);
from Crypto.Cipher import AES
from Crypto import Random
import hashlib
from binascii import hexlify, unhexlify
BLOCK_SIZE = AES.block_size # Bytes
salt = unhexlify("3fb8877d37fdc04e4a4765EFb8ab7d36")
class PaddingError(Exception):
"""Exception raised for errors in the padding.
Attributes:
message -- explanation of the error
"""
def __init__(self, message):
self.message = message
pad = lambda s: s + ((BLOCK_SIZE - len(s) % BLOCK_SIZE) *
chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)).encode('ascii')
def unpad(s):
in_len = len(s)
if in_len == 0:
raise PaddingError("empty input")
pad_char = s[-1]
if pad_char > BLOCK_SIZE:
raise PaddingError("padding length > 16")
for i in s[in_len - pad_char:]:
if i != pad_char:
raise PaddingError("unexpected padding char")
return s[:-pad_char]
# kdf does 2 times sha256 and takes the first 16 bytes
def kdf(raw_key):
"""
kdf does 2 times sha256 and takes the first 16 bytes
:param raw_key:
:return:
"""
return hashlib.sha256(hashlib.sha256(raw_key + salt).digest()).digest()[:16]
def encrypt(raw, password):
"""
encrypt encrypts data with given password by AES-128-CBC PKCS#7, iv will be placed
at head of cipher data.
:param raw: input raw byte array
:param password: password byte array
:return: encrypted byte array
"""
iv = Random.new().read(AES.block_size)
cipher = AES.new(kdf(password), AES.MODE_CBC, iv)
return iv + cipher.encrypt(pad(raw))
def decrypt(enc, password):
"""
decrypt decrypts data with given password by AES-128-CBC PKCS#7. iv will be read from
the head of raw.
:param enc: input encrypted byte array
:param password: password byte array
:return: decrypted byte array
"""
iv = enc[:16]
cipher = AES.new(kdf(password), AES.MODE_CBC, iv)
return unpad(cipher.decrypt(enc[16:]))
/*
* Copyright 2019 The CovenantSQL Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except raw compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to raw writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package toolkit
import (
"bytes"
"crypto/aes"
"encoding/hex"
"testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/CovenantSQL/CovenantSQL/utils/log"
)
// Test cases for all implementations.
// Because iv is random, so Encrypted data is not always the same,
// but Decrypt(possibleEncrypted) will get raw.
// `raw` and `possibleEncrypted` are in hex
// `pass` is raw string
var testCases = []struct {
raw string
pass string
possibleEncrypted string
}{
{
raw: "11",
pass: ";#K]As9C*6L",
possibleEncrypted: "a372ea2c158a2f99d386e309db4355a659a7a8dd3986fd1d94f7604256061609",
},
{
raw: "111282C128421286712857128C2128EF" +
"128B7671283C128571287512830128EC" +
"128391281A1312849128381281E1286A" +
"12871128621287A9D12857128C412886" +
"128FD12834128DA128F5",
pass: "",
possibleEncrypted: "1bfb6a7fda3e3eb1e14c9afd0baefe86" +
"c90979101f179db7e48a0fa7617881e8" +
"f752c59fb512bb86b8ed69c5644bf2dc" +
"30fbcd3bf79fb20342595c84fad00e46" +
"2fab3e51266492a3d5d085e650c1e619" +
"6278d7f5185c263440ec6fd940ffbb85",
},
{
raw: "11",
pass: "'K]\"#'pi/1/JD2",
possibleEncrypted: "a83d152777ce3a1c0710b03676ae867c86ab0a47b3ca080f825683ac1079eb41",
},
{
raw: "11111111111111111111111111111111",
pass: "",
possibleEncrypted: "7dda438c4256a63c62d6816617fcbf9c" +
"7773b9b4f87902b7253848ba2b0ed0ba" +
"f70a3ac976a835b7bc3008e9ba43da74",
},
{
raw: "11111111111111111111111111111111",
pass: "youofdas1312",
possibleEncrypted: "cab07967cf377dbc010fbf5f84d12bcb" +
"6f8b188e6965738cf9007a671b4bfeb9" +
"f52257aac3808048c341dcaa1c125ca7",
},
{
raw: "11111111111111111111111111",
pass: "空のBottle😄",
possibleEncrypted: "4384874473945c5b70519ad5ace6305ef6b78c60c3c694add08a8b81899c4171",
},
}
func TestEncryptDecryptCases(t *testing.T) {
defaultLevel := log.GetLevel()
log.SetLevel(log.DebugLevel)
defer log.SetLevel(defaultLevel)
Convey("encrypt & decrypt cases", t, func() {
for i, c := range testCases {
in, _ := hex.DecodeString(c.raw)
pass := []byte(c.pass)
out, _ := hex.DecodeString(c.possibleEncrypted)
log.Infof("TestEncryptDecryptCases: %d", i)
enc, err := Encrypt(in, pass)
log.Debugf("Enc: %x", enc)
So(err, ShouldBeNil)
dec1, err := Decrypt(enc, pass)
if !bytes.Equal(dec1, in) {
t.Errorf("\nExpected:\n%x\nActual:\n%x\n", in, dec1)
}
dec2, err := Decrypt(out, pass)
So(err, ShouldBeNil)
if !bytes.Equal(dec2, in) {
t.Errorf("\nExpected:\n%x\nActual:\n%x\n", in, dec2)
}
}
})
}
func TestEncryptDecrypt(t *testing.T) {
var password = "CovenantSQL.io"
Convey("encrypt & decrypt 0 length string with aes128", t, func() {
enc, err := Encrypt([]byte(""), []byte(password))
So(enc, ShouldNotBeNil)
So(len(enc), ShouldEqual, 2*aes.BlockSize)
So(err, ShouldBeNil)
dec, err := Decrypt(enc, []byte(password))
So(dec, ShouldNotBeNil)
So(len(dec), ShouldEqual, 0)
So(err, ShouldBeNil)
})
Convey("encrypt & decrypt 0 length bytes with aes128", t, func() {
enc, err := Encrypt([]byte(nil), []byte(password))
So(enc, ShouldNotBeNil)
So(len(enc), ShouldEqual, 2*aes.BlockSize)
So(err, ShouldBeNil)
dec, err := Decrypt(enc, []byte(password))
So(dec, ShouldNotBeNil)
So(len(dec), ShouldEqual, 0)
So(err, ShouldBeNil)
})
Convey("encrypt & decrypt 1 byte with aes128", t, func() {
enc, err := Encrypt([]byte{0x11}, []byte(password))
So(enc, ShouldNotBeNil)
So(len(enc), ShouldEqual, 2*aes.BlockSize)
So(err, ShouldBeNil)
dec, err := Decrypt(enc, []byte(password))
So(dec, ShouldResemble, []byte{0x11})
So(len(dec), ShouldEqual, 1)
So(err, ShouldBeNil)
})
Convey("encrypt & decrypt 1747 length bytes", t, func() {
in := bytes.Repeat([]byte{0xff}, 1747)
enc, err := Encrypt(in, []byte(password))
So(enc, ShouldNotBeNil)
So(len(enc), ShouldEqual, (1747/aes.BlockSize+2)*aes.BlockSize)
So(err, ShouldBeNil)
dec, err := Decrypt(enc, []byte(password))
So(dec, ShouldResemble, in)
So(len(dec), ShouldEqual, 1747)
So(err, ShouldBeNil)
})
Convey("encrypt & decrypt 32 length bytes", t, func() {
in := bytes.Repeat([]byte{0xcc}, 32)
enc, err := Encrypt(in, []byte(password))
So(enc, ShouldNotBeNil)
So(len(enc), ShouldEqual, (32/aes.BlockSize+2)*aes.BlockSize)
So(err, ShouldBeNil)
dec, err := Decrypt(enc, []byte(password))
So(dec, ShouldResemble, in)
So(len(dec), ShouldEqual, 32)
So(err, ShouldBeNil)
})
Convey("decrypt error length bytes", t, func() {
in := bytes.Repeat([]byte{0xaa}, 1747)
dec, err := Decrypt(in, []byte(password))
So(dec, ShouldBeNil)
So(err.Error(), ShouldEqual, "cipher data size not match")
})
}
/*
* Copyright 2019 The CovenantSQL Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.covenantsql.connector;
import io.covenantsql.connector.util.EndToEndEncryption;
import org.apache.commons.codec.binary.Hex;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
class testCase {
String raw;
String password;
String possibleEncrypted;
public testCase(String raw, String password, String possibleEncrypted) {
this.raw = raw;
this.password = password;
this.possibleEncrypted = possibleEncrypted;
}
}
public class EndToEndEncryptionTests {
testCase[] cases;
@BeforeMethod
public void setUp() {
cases = new testCase[]{
new testCase(
"11",
";#K]As9C*6L",
"a372ea2c158a2f99d386e309db4355a659a7a8dd3986fd1d94f7604256061609"
),
new testCase(
"111282C128421286712857128C2128EF" +
"128B7671283C128571287512830128EC" +
"128391281A1312849128381281E1286A" +
"12871128621287A9D12857128C412886" +
"128FD12834128DA128F5",
"",
"1bfb6a7fda3e3eb1e14c9afd0baefe86" +
"c90979101f179db7e48a0fa7617881e8" +
"f752c59fb512bb86b8ed69c5644bf2dc" +
"30fbcd3bf79fb20342595c84fad00e46" +
"2fab3e51266492a3d5d085e650c1e619" +
"6278d7f5185c263440ec6fd940ffbb85"
),
new testCase(
"11",
"'K]\"#'pi/1/JD2",
"a83d152777ce3a1c0710b03676ae867c86ab0a47b3ca080f825683ac1079eb41"
),
new testCase(
"11111111111111111111111111111111",
"",
"7dda438c4256a63c62d6816617fcbf9c" +
"7773b9b4f87902b7253848ba2b0ed0ba" +
"f70a3ac976a835b7bc3008e9ba43da74"
),
new testCase(
"11111111111111111111111111111111",
"youofdas1312",
"cab07967cf377dbc010fbf5f84d12bcb" +
"6f8b188e6965738cf9007a671b4bfeb9" +
"f52257aac3808048c341dcaa1c125ca7"
),
new testCase(
"11111111111111111111111111",
"空のBottle😄",
"4384874473945c5b70519ad5ace6305ef6b78c60c3c694add08a8b81899c4171"
),
};
}
@Test
public void EncryptDecrypt() throws Exception {
for (int i = 0; i < cases.length; i++) {
byte[] raw = Hex.decodeHex(cases[i].raw.toCharArray());
byte[] password = cases[i].password.getBytes();
byte[] enc = Hex.decodeHex(cases[i].possibleEncrypted.toCharArray());
byte[] encrypt = EndToEndEncryption.Encrypt(raw, password);
byte[] dec = EndToEndEncryption.Decrypt(encrypt, password);
byte[] dec2 = EndToEndEncryption.Decrypt(enc, password);
assertEquals(dec, raw);
assertEquals(dec2, raw);
System.out.printf("Test case: #%d Passed\n", i);
}
}
}
/*
* Copyright 2019 The CovenantSQL Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.covenantsql.connector.util;
import java.security.*;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class EndToEndEncryption {
private static final SecureRandom random = new SecureRandom();
private static final byte[] salt = new byte[]{
(byte) 0x3f, (byte) 0xb8, (byte) 0x87, (byte) 0x7d, (byte) 0x37, (byte) 0xfd, (byte) 0xc0, (byte) 0x4e,
(byte) 0x4a, (byte) 0x47, (byte) 0x65, (byte) 0xEF, (byte) 0xb8, (byte) 0xab, (byte) 0x7d, (byte) 0x36
};
private static byte[] generateIV() {
byte[] ivBytes = new byte[16];
random.nextBytes(ivBytes);
return ivBytes;
}
private static byte[] kdf(byte[] rawPass) {
MessageDigest sha = null;
MessageDigest final_sha = null;
byte[] key = new byte[16];
try {
sha = MessageDigest.getInstance("SHA-256");
final_sha = MessageDigest.getInstance("SHA-256");
sha.update(rawPass);
sha.update(salt);
final_sha.update(sha.digest());
System.arraycopy(final_sha.digest(), 0, key, 0, 16);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return key;
}
public static byte[] Encrypt(final byte[] raw,
final byte[] key) {
try {
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final int blockSize = cipher.getBlockSize();
// create the key
final SecretKeySpec symKey = new SecretKeySpec(kdf(key), "AES");
// generate random IV using block size (possibly create a method for
// this)
final byte[] ivData = new byte[blockSize];
final SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
rnd.nextBytes(ivData);
final IvParameterSpec iv = new IvParameterSpec(ivData);
cipher.init(Cipher.ENCRYPT_MODE, symKey, iv);
final byte[] encryptedMessage = cipher.doFinal(raw);
// concatenate IV and encrypted message
final byte[] ivAndEncryptedMessage = new byte[ivData.length
+ encryptedMessage.length];
System.arraycopy(ivData, 0, ivAndEncryptedMessage, 0, blockSize);
System.arraycopy(encryptedMessage, 0, ivAndEncryptedMessage,
blockSize, encryptedMessage.length);
return ivAndEncryptedMessage;
} catch (InvalidKeyException e) {
throw new IllegalArgumentException(
"key argument does not contain a valid AES key");
} catch (GeneralSecurityException e) {
throw new IllegalStateException(
"Unexpected exception during encryption", e);
}
}
public static byte[] Decrypt(final byte[] enc,
final byte[] key) {
try {
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final int blockSize = cipher.getBlockSize();
// create the key
final SecretKeySpec symKey = new SecretKeySpec(kdf(key), "AES");
// retrieve random IV from start of the received message
final byte[] ivData = new byte[blockSize];
System.arraycopy(enc, 0, ivData, 0, blockSize);
final IvParameterSpec iv = new IvParameterSpec(ivData);
// retrieve the encrypted message itself
final byte[] encryptedMessage = new byte[enc.length
- blockSize];
System.arraycopy(enc, blockSize,
encryptedMessage, 0, encryptedMessage.length);
cipher.init(Cipher.DECRYPT_MODE, symKey, iv);
return cipher.doFinal(encryptedMessage);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException(
"key argument does not contain a valid AES key");
} catch (BadPaddingException e) {
// you'd better know about padding oracle attacks
return null;
} catch (GeneralSecurityException e) {
throw new IllegalStateException(
"Unexpected exception during decryption", e);
}
}
}
# coding: utf-8
import unittest
from pycovenantsql.e2ee import encrypt, decrypt, unpad, PaddingError
from binascii import hexlify, unhexlify
# Test cases for all implementations.
# Because iv is random, so Encrypted data is not always the same,
# but Decrypt(possibleEncrypted) will get raw.
cases = [
{
"raw": "11",
"pass": ";#K]As9C*6L",
"possibleEncrypted": "a372ea2c158a2f99d386e309db4355a659a7a8dd3986fd1d94f7604256061609",
},
{
"raw": "111282C128421286712857128C2128EF" +
"128B7671283C128571287512830128EC" +
"128391281A1312849128381281E1286A" +
"12871128621287A9D12857128C412886" +
"128FD12834128DA128F5",
"pass": "",
"possibleEncrypted": "1bfb6a7fda3e3eb1e14c9afd0baefe86" +
"c90979101f179db7e48a0fa7617881e8" +
"f752c59fb512bb86b8ed69c5644bf2dc" +
"30fbcd3bf79fb20342595c84fad00e46" +
"2fab3e51266492a3d5d085e650c1e619" +
"6278d7f5185c263440ec6fd940ffbb85",
},
{
"raw": "11",
"pass": "'K]\"#'pi/1/JD2",
"possibleEncrypted": "a83d152777ce3a1c0710b03676ae867c86ab0a47b3ca080f825683ac1079eb41",
},
{
"raw": "11111111111111111111111111111111",
"pass": "",
"possibleEncrypted": "7dda438c4256a63c62d6816617fcbf9c" +
"7773b9b4f87902b7253848ba2b0ed0ba" +
"f70a3ac976a835b7bc3008e9ba43da74",
},
{
"raw": "11111111111111111111111111111111",
"pass": "youofdas1312",
"possibleEncrypted": "cab07967cf377dbc010fbf5f84d12bcb" +
"6f8b188e6965738cf9007a671b4bfeb9" +
"f52257aac3808048c341dcaa1c125ca7",
},
{
"raw": "11111111111111111111111111",
"pass": "空のBottle😄",
"possibleEncrypted": "4384874473945c5b70519ad5ace6305ef6b78c60c3c694add08a8b81899c4171",
},
]
class TestE2ee(unittest.TestCase):
def test_enc_dec(self):
i = 0
for case in cases:
print("Case: #" + str(i))
i += 1
enc = encrypt(unhexlify(case["raw"]), case["pass"].encode())
dec = decrypt(enc, case["pass"].encode())
self.assertEqual(unhexlify(case["raw"]), dec)
dec2 = decrypt(unhexlify(case["possibleEncrypted"]), case["pass"].encode())
self.assertEqual(unhexlify(case["raw"]), dec2)
def test_unpad_error(self):
self.assertEqual(
unpad(unhexlify("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa01")),
unhexlify("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
)
self.assertEqual(
unpad(unhexlify("aaaaaaaaaaaaaaaaaaaaaaaaaaaa0202")),
unhexlify("aaaaaaaaaaaaaaaaaaaaaaaaaaaa")
)
self.assertRaisesRegex(PaddingError, "unexpected padding char", unpad, unhexlify("aaaaaaaaaaaaaaaaaaaaaaaaaaaa0102"))
self.assertRaisesRegex(PaddingError, "padding length > 16", unpad, unhexlify("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
self.assertRaisesRegex(PaddingError, "empty input", unpad, unhexlify(""))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment