I want to access data in a database created by Rails for use by non-Ruby code. Some fields use attr_encrypted accessors, and the library in use is the symmetric-encryption gem. I consistently get a "wrong final block length" error if I try to decrypt the data with, e.g., the NodeJS crypto library.
I suspect this has to do either with character encoding or with padding, but I can't figure it out based on the docs.
As an experiment, I tried decrypting data from symmetric-encryption in Ruby's own OpenSSL library, and I get either a "bad decrypt" error or the same problem:
SymmetricEncryption.cipher = SymmetricEncryption::Cipher.new(
key: "1234567890ABCDEF",
iv: "1234567890ABCDEF",
cipher_name: "aes-128-cbc"
)
ciphertext = SymmetricEncryption.encrypt("Hello world")
c = OpenSSL::Cipher.new("aes-128-cbc")
c.iv = c.key = "1234567890ABCDEF"
c.update(ciphertext) + c.final
That gives me a "bad decrypt" error.
Interestingly, the encrypted data in the database can be decrypted by the symmetric-encryption gem, but isn't the same as the output of SymmetricEncryption.encrypt (and OpenSSL doesn't successfully decrypt it, either).
Edit:
psql=# SELECT "encrypted_firstName" FROM people LIMIT 1;
encrypted_firstName
----------------------------------------------------------
QEVuQwBAEAAuR5vRj/iFbaEsXKtpjubrWgyEhK5Pji2EWPDPoT4CyQ==
(1 row)
Then
irb> SymmetricEncryption.decrypt "QEVuQwBAEAAuR5vRj/iFbaEsXKtpjubrWgyEhK5Pji2EWPDPoT4CyQ=="
=> "Lurline"
irb> SymmetricEncryption.encrypt "Lurline"
=> "QEVuQwAAlRBeYptjK0Fg76jFQkjLtA=="
Looking at the source for the symmetric-encryption gem, by default it adds a header to the output and base64 encodes it, although both of these are configurable.
To decrypt using Ruby’s OpenSSL directly, you will need to decode it and strip off this header, which is 6 bytes long in this simple case:
ciphertext = Base64.decode64(ciphertext)
ciphertext = ciphertext[6..-1]
c = OpenSSL::Cipher.new("aes-128-cbc")
c.decrypt
c.iv = "1234567890ABCDEF"
c.key = "1234567890ABCDEF"
result = c.update(ciphertext) + c.final
Of course, you may need to alter this depending on what settings you are using in symmetric-encryption, e.g. the header length may vary. In order to decrypt the result from the database you will need to parse the header. Have a look at the source.
Based on the Rust implementation done by #Shepmaster in my other question (and the source code for the symmetric-encryption gem), I have a working version in TypeScript. #matt is close with his answer, but the header can actually have additional bytes containing metadata about the encrypted data. Note that this doesn't handle (1) compressed encrypted data, or (2) setting the encryption algorithm from the header itself; neither situation is relevant to my use case.
import { createDecipher, createDecipheriv, Decipher } from "crypto";
// We use two types of encoding with SymmetricEncryption: Base64 and UTF-8. We
// define them in an `enum` for type safety.
const enum Encoding {
Base64 = "base64",
Utf8 = "utf8",
}
// Symmetric encryption's header contains the following data:
interface IHeader {
version: number, // The version of the encryption algo
isCompressed: boolean, // Whether the data is compressed (TODO: Implement)
hasIv: boolean, // Whether the header itself has the IV
hasKey: boolean, // Whether the header itself has the Key
hasCipherName: boolean, // Whether the header contains the cipher name
hasAuthTag: boolean, // Whether the header has an authorization tag
offset: number, // How many bytes into the encoded ciphertext the actual encrypted data starts
iv?: Buffer, // The IV, present only if `hasIv` is true
key?: Buffer, // The key, present only if `hasKey` is true
// The cipher name, present only if `hasCipherName` is true. Currently ignored.
cipherName?: string,
authTag?: string, // The authorization tag, present only if // `hasAuthTag` is true
}
// Byte 6 of the header contain bit flags
interface IFlags {
isCompressed: boolean,
hasIv: boolean,
hasKey: boolean,
hasCipherName: boolean,
hasAuthTag: boolean
}
// The 7th byte until the end of the header have the actual values. If all
// of the flags are false, the header ends at the 6th byte.
interface IValues {
iv?: Buffer,
key?: Buffer,
cipherName?: string,
authTag?: string,
size: number,
}
/**
* Represent the encoded ciphertext, complete with the SymmetricEncryption header.
*/
class Ciphertext {
// Bit flags corresponding to the data encoded in byte 6 of the
// header.
readonly FLAG_COMPRESSED = 0b1000_0000;
readonly FLAG_IV = 0b0100_0000;
readonly FLAG_KEY = 0b0010_0000;
readonly FLAG_CIPHER_NAME = 0b0001_0000;
readonly FLAG_AUTH_TAG = 0b0000_1000;
// The literal data encoded in bytes 1 - 4 of the header
readonly MAGIC_HEADER = "#EnC";
// If any of the values represented by the bit flags is present, the first 2
// bytes of the data tells us how long the actual value is. In other words,
// the first 2 bytes aren't the value itself, but rather give the info about
// the length of the rest of the value.
readonly LENGTH_INFO_SIZE = 2;
public header: IHeader | null;
public data: Buffer;
private cipherBuffer: Buffer;
constructor(private input: string) {
this.cipherBuffer = new Buffer(input, Encoding.Base64);
this.header = this.getHeader();
const offset = this.header ? this.header.offset : 0; // If no header, then no offset
this.data = this.cipherBuffer.slice(offset);
}
/**
* Extract the header from the data
*/
private getHeader(): IHeader | null {
let offset = 0;
// Bytes 1 - 4 are the literal `#EnC`. If that's absent, there's no
// SymmetricEncryption header.
if (this.cipherBuffer.toString(Encoding.Utf8, offset, offset += 4) != this.MAGIC_HEADER) {
return null;
}
// Byte 5 is the version
const version = this.cipherBuffer.readInt8(offset++); // Post increment
// Byte 6 is the flags
const rawFlags = this.cipherBuffer.readInt8(offset++);
const flags = this.readFlags(rawFlags);
// Bytes 7 - end are the values.
const values = this.getValues(offset, flags);
offset += values.size;
return Object.assign({ version, offset }, flags, values);
}
/**
* Get the values for `iv`, `key`, `cipherName`, and `authTag`, if any are
* set, based on the bitflags. Return that data, plus how many bytes in the
* header those values represent.
*
* #param offset - What byte we're on when we get to the values. Should be 7
* #param flags - The flags we've extracted, showing us which values to expect
*/
private getValues(offset: number, flags: IFlags): IValues {
let iv: Buffer | undefined = undefined;
let key: Buffer | undefined = undefined;
let cipherName: string | undefined = undefined;
let authTag: string | undefined = undefined;
let size = 0; // If all of the bit flags are false, there is no additional data.
// For each value, see if the flag is set to true. If it is, we need to
// read the value. Keys and IVs need to be `Buffer` types; other values
// should be strings.
[iv, size] = flags.hasIv ? this.readBuffer(offset) : [undefined, size];
[key, size] = flags.hasKey ? this.readBuffer(offset + size) : [undefined, size];
[cipherName, size] = flags.hasCipherName ? this.readString(offset + size) : [undefined, size];
[authTag, size] = flags.hasAuthTag ? this.readString(offset + size) : [undefined, size];
return { iv, key, cipherName, authTag, size };
}
/**
* Parse the 16-bit integer representing the bit flags into an object for
* easier handling
*
* #param flags - The 16-bit integer that contains the bit flags
*/
private readFlags(flags: number): IFlags {
return {
isCompressed: (flags & this.FLAG_COMPRESSED) != 0,
hasIv: (flags & this.FLAG_IV) != 0,
hasKey: (flags & this.FLAG_KEY) != 0,
hasCipherName: (flags & this.FLAG_CIPHER_NAME) != 0,
hasAuthTag: (flags & this.FLAG_AUTH_TAG) != 0
}
}
/**
* Read a string out of the value at the specified offset. Return the value
* itself, plus the number of bytes consumed by the value (including the
* 2-byte encoding of the length of the actual value).
*
* #param offset - The offset (bytes from the beginning of the encoded,
* encrypted Buffer) at which the value in question begins
*/
private readString(offset: number): [string, number] {
// The length is the first 2 bytes, encoded as a little-endian 16-bit integer
const length = this.cipherBuffer.readInt16LE(offset);
// The total size occupied in the header is the 2 bytes encoding length plus the length itself
const size = this.LENGTH_INFO_SIZE + length;
const value = this.cipherBuffer.toString(Encoding.Base64, offset + this.LENGTH_INFO_SIZE, offset + size);
return [value, size];
}
/**
* Read a Buffer out of the value at the specified offset. Return the value
* itself, plus the number of bytes consumed by the value (including the
* 2-byte encoding of the length of the actual value).
*
* #param offset - The offset (bytes from the beginning of the encoded,
* encrypted Buffer) at which the value in question begins
*/
private readBuffer(offset: number): [Buffer, number] {
// The length is the first 2 bytes, encoded as a little-endian 16-bit integer
const length = this.cipherBuffer.readInt16LE(offset);
// The total size occupied in the header is the 2 bytes encoding length plus the length itself
const size = this.LENGTH_INFO_SIZE + length;
const value = this.cipherBuffer.slice(offset + this.LENGTH_INFO_SIZE, offset + size);
return [value, size];
}
}
/**
* Allow decryption of data encrypted by Ruby's `symmetric-encryption` gem
*/
class SymmetricEncryption {
private key: Buffer;
private iv?: Buffer;
constructor(key: string, private algo: string, iv?: string) {
this.key = new Buffer(key);
this.iv = iv ? new Buffer(iv) : undefined;
}
public decrypt(input: string): string {
const ciphertext = new Ciphertext(input);
// IV can be specified by the user. But if it's encoded in the header
// itself, go with that instead.
const iv = (ciphertext.header && ciphertext.header.iv) ? ciphertext.header.iv : this.iv;
// Key can be specified by the user. but if it's encoded in the header,
// go with that instead.
const key = (ciphertext.header && ciphertext.header.key) ? ciphertext.header.key : this.key;
const decipher: Decipher = iv ?
createDecipheriv(this.algo, key, iv) :
createDecipher(this.algo, key);
// Terse version of `update()` + `final()` that passes type checking
return Buffer.concat([decipher.update(ciphertext.data), decipher.final()]).toString();
}
}
const s = new SymmetricEncryption("1234567890ABCDEF", "aes-128-cbc", "1234567890ABCDEF");
console.log(s.decrypt("QEVuQwAADWK0cKzgFIovdIThq9Scrg==")); // => "Hello world"
I'm using ActiveSupport::MessageEncryptor to encrypt a string in Ruby as follows:
salt = SecureRandom.random_bytes(64)
// => "s\x90L\xB8\xEF\x8BBp\xB6\xF5A\x95\xA8]+\x94\xF3\xA7\x9A\x84+jC\xBF\xB0\x15\xEF*\x8C\xDD.\xE5\xC7Y\xCE\xE1\xAA\xA4I/%.\x9E\x14\xC1\xA8\x9E\x122\xE0\x19.\x19\xD8\xB6\xE8\x83\xE1\xFE\x16\xB5\x11N\x18"
key = ActiveSupport::KeyGenerator.new('password').generate_key(salt)
// => "\x12\xBD1\xA0Q\xBF)\\\x89\xDF\x95\xD0\f\x03\x17P'\x87\xAD\x92b\xB5%\xC7X\x01\x9Ar\xCB\xC9\x1A\x10'\xC4\x95w\xBF\xED]\x17\xEB\x9F#\xC6\xEE8S\xE1^\x18\xE2^\x85Z\rJ\x9A\xEE\xA5\xEC|\xA2\xA9\x8E"
crypt = ActiveSupport::MessageEncryptor.new(key)
encrypted_username = crypt.encrypt_and_sign("daniel")
// => "N0dHcFM3MnQrcW1HUk9UTGwxeUJsZmlCNzcwUGhrdUdtbE9YWnUxamZFST0tLUVUcUlIU2k1ZHIvTmlDRUgzM2FsS0E9PQ==--1ede80eb2b498ddf5133f8f3a45a82db2476c740"
Then in Scala I'm trying to decrypt like so:
var encrypted_str = "N0dHcFM3MnQrcW1HUk9UTGwxeUJsZmlCNzcwUGhrdUdtbE9YWnUxamZFST0tLUVUcUlIU2k1ZHIvTmlDRUgzM2FsS0E9PQ==--1ede80eb2b498ddf5133f8f3a45a82db2476c740"
val parts = encrypted_str.split("--");
val encryptedData = Base64.decodeBase64(parts(0))
val iv = Base64.decodeBase64(parts(1))
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(iv.take(16)));
val result = cipher.doFinal(encryptedData);
println(new String(result, "UTF-8"))
But I run into this error:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
I think the problem might be here:
val iv = Base64.decodeBase64(parts(1))
The second part of your message is 1ede80eb2b498ddf5133f8f3a45a82db2476c740 which is not a base 64 encoded string, it's a hex encoded string. Try this instead:
Hex.decodeHex(parts(1))
This is my code
def encrypt(q,_userEmail,password):
"""Mimic Java's PBEWithMD5AndDES algorithm to produce a DES key"""
_password = '111'
_salt = '\xA9\x9B\xC8\x32\x56\x35\xE3\x03'
_iterations = 19
plaintext_to_encrypt = password
hasher = MD5.new()
hasher.update(_password)
hasher.update(_salt)
result = hasher.digest()
for i in range(1, _iterations):
hasher = MD5.new()
hasher.update(result)
result = hasher.digest()
# Pad plaintext per RFC 2898 Section 6.1
padding = 8 - len(plaintext_to_encrypt) % 8
plaintext_to_encrypt += chr(padding) * padding
encoder = DES.new(result[:8], DES.MODE_CBC, result[8:16])
encrypted = encoder.encrypt(plaintext_to_encrypt)
q.put(encrypted.encode('base64'))
pw = encrypted.encode('base64')
print str(pw)
cur = db.cursor()
cur.execute('''SELECT * FROM UTTARA_USER_MAST WHERE EMAIL_ID=:1 AND PASSWD=:2''',(_userEmail,str(pw),))
data=cur.fetchall()
print data
print str(pw) prints the encrypted password ,when i pass pw in a query
the query returns blank but when i give the encrypted string directly in query it returns the data
edit: I use Oracle DB
I tried to get the image from clipboard when I press ctrl+v on a page, with the code from a gist, which will console.log the image content it gets.
I found the image content is a long base64 string with a special prefix:
data:image/png;base64,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
How to decode it to binary? I tried to remove the prefix data:image/png;base64, and use a base64 library to decode the rest into binary, and save it to a image.png file. But the file can't be displayed, with invalid format error.
But if I paste the whole string to http://base64online.org/decode/, it can play the image as well.
The base64 library I used is https://github.com/wstrange/base64, and my code is:
import 'package:base64_codec/base64_codec.dart';
var body = /* the long str above */;
var prefix = "data:image/png;base64,";
var bStr = body.substring(prefix.length);
var bs = Base64Codec.codec.decodeList(bStr.codeUnits);
var file = new File("image.png");
file.writeAsBytesSync(bs);
I don't know where is wrong :(
Try using the crypto package from the Dart SDK. This creates an image.png that opens fine (I believe it's this image... )
library foo;
import 'package:crypto/crypto.dart';
import 'dart:io';
void main() {
var body = "data:image/png;base64,iVBORw0KGgoAAAANSUh... snip";
var prefix = "data:image/png;base64,";
var bStr = body.substring(prefix.length);
var bytes = CryptoUtils.base64StringToBytes(bStr); // using CryptoUtils from Dart SDK
var file = new File("image.png");
file.writeAsBytesSync(bytes);
}
You'll need to add crypto to your pubspec.yaml :
dependencies:
crypto: any
The library linked at https://github.com/wstrange/base64, also notes in the README:
Deprecated
Note: The Dart SDK now includes a Base64 codec with url safe options. Unless you need streaming support, you should use the SDK methods as they are slightly faster.
See CryptoUtils
See also How to native convert string -> base64 and base64 -> string
Decoding as a string and then writing the bytes works:
var prefix = "data:image/png;base64,";
var bStr = body.substring(prefix.length);
var bs = Base64Codec.codec.decodeString(bStr);
var file = new File("image.png");
file.writeAsBytesSync(bs.codeUnits);
The easiest approach is:
import 'dart:convert';
final result = base64Decode(stringValue);
Using JMeter, I would like to take values from CSV file, concatenate the values and do a MD5 hash on them and then send the value as part of HTTP request using HTTP Request Sampler.
I tried the following but did not get the correct result:
created CSV Data Set Config and added the variables csvVal1,csvVal2,csvVal3;
in the jp#gc-Dummy Sampler i added the following:
${__MD5(${csvval1}+${csvval2}+${csvval3})}
This did not work, what is the right way?
I ended up using BeanShell Preporcessor and used the following script
import java.security.MessageDigest;
String val1 = vars.get("csv_val1");
String val2 = vars.get("csv_val2");
String val3 = vars.get("csv_val3");
String totalString = val1+val2+val3;
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] md5hash = new byte[32];
md.update(totalString.getBytes("utf-8"), 0, totalString.length());
md5hash = md.digest();
StringBuffer sb = new StringBuffer();
for (int i=0;i<md5hash.length;i++) {
String sval = Integer.toHexString((int) md5hash[i] & 0xFF);
if(sval.length()== 1)
{
sval = "0"+sval;
}
sb.append(sval);
}
log.info("tktest: "+ sb);
vars.putObject("MD5Signature", sb.toString());
There's a new function __digest, currently in nightly builds
In your case to save in MD5Signature variable the result of 3 variable use the following:
${__digest(MD5,${csv_val1}${csv_val2}${csv_val3},,,MD5Signature)}