Implementing PBEWithMD5AndDES in Ruby

I'm trying to get a ruby ​​implementation of an encryption library that is apparently popular in the Java world - PBEWithMD5AndDES

Does anyone know how to use openssl or another open source stone to perform encryption / decryption compatible with this format?

Updated:

I used gem chilkat to implement it, but it’s paid, I need an open source solution.

+4
source share
5 answers

You do not need to actually implement PBEWithMD5andDES, assuming ruby ​​has a DES implementation. What you need to implement is the key derivation function (who you get the key from the password), and then pass this derived key to DES with the appropriate mode and addition.

Fortunately, the key derivation function is not particularly important for security during implementation, so you can do it yourself safely enough. According to rfc , PBEwithMD5AndDES is actually PBKDF1 (kera output function) used with DES in CBC mode.

PBKDF1 doesn't look so hard to implement. It looks like you can do this with a for loop and calling md5.

Note that you can still get some odd results due to the possibility of using a different padding scheme in Java and Ruby. I assume spec one is a complement to pkcs 1.5, but with a quick glance I cannot confirm this.

5.1 PBKDF1

PBKDF1 applies the hash function, which must be MD2 [6], MD5 [19] or SHA-1 [18] in order to receive the keys. Derived Key Length Limited
the hash function output length, which is 16 octets for MD2 and MD5 and 20 octets for SHA-1. PBKDF1 is a key-compatible derivation process in PKCS No. 5 v1.5.

PBKDF1 is recommended only for compatibility with existing
applications because the keys it produces may not be large enough for
some applications.

PBKDF1 (P, S, c, dkLen)

Parameters: Hash hash function

Input: password P, octet string S salt, eight-octave thread c iteration, positive integer dkLen expected length in octets of the derived key, positive integer, not more than 16 for MD2 or MD5 and 20 for SHA-1

Conclusion: the key generated by DK, dkLen octet string

Steps:

1. If dkLen > 16 for MD2 and MD5, or dkLen > 20 for SHA-1, output "derived key too long" and stop. 2. Apply the underlying hash function Hash for c iterations to the concatenation of the password P and the salt S, then extract the first dkLen octets to produce a derived key DK: T_1 = Hash (P || S) , T_2 = Hash (T_1) , ... T_c = Hash (T_{c-1}) , DK = Tc<0..dkLen-1> 3. Output the derived key DK. 
+2
source

I know that it is very old, but I had the same problem, and I just decided, for encryption, where salt is your salt bite, the access key is your password key string, and iterations are the number of iterations you want to use

 def encrypt_account_number cipher = OpenSSL::Cipher::Cipher.new("DES") cipher.encrypt cipher.pkcs5_keyivgen passkey, salt,iterations,digest encrypted_account_number = cipher.update(account_number) encrypted_account_number << cipher.final Base64.encode64(encrypted_account_number ) end def decrypt_account_number cipher = OpenSSL::Cipher::Cipher.new("DES") base_64_code = Base64.decode64(account_number) cipher.decrypt cipher.pkcs5_keyivgen passkey, salt,iterations,digest decrypted_account_number = cipher.update base_64_code decrypted_account_number << cipher.final decrypted_account_number end 
+2
source

For what it's worth, I am posting my Python code that actually works (I have tons of encrypted values ​​that were executed using org.jasypt.util.text.BasicTextEncryptor, I needed to decrypt them).

 import base64 import hashlib from Crypto.Cipher import DES """ Note about PBEWithMD5AndDES in java crypto library: Encrypt: Generate a salt (random): 8 bytes <start derived key generation> Append salt to the password MD5 Hash it, and hash the result, hash the result ... 1000 times MD5 always gives us a 16 byte hash Final result: first 8 bytes is the "key" and the next is the "initialization vector" (there is something about the first 8 bytes needing to be of odd paraity, therefore the least significant bit needs to be changed to 1 if required. We don't do it, maybe the python crypto library does it for us) <end derived key generation> Pad the input string with 1-8 bytes (note: not 0-7, so we always have padding) so that the result is a multiple of 8 bytes. Padding byte value is same as number of bytes being padded, eg, \x07 if 7 bytes need to be padded. Use the key and iv to encrypt the input string, using DES with CBC mode. Prepend the encrypted value with the salt (needed for decrypting since it is random) Base64 encode it -> this is your result Decrypt: Base64 decode the input message Extract the salt (first 8 bytes). The rest is the encoded text. Use derived key generation as in Encrypt above to get the key and iv Decrypt the encoded text using key and iv Remove padding -> this is your result (I only have implemented decrypt here since that all I needed, but encrypt should be straighforward as well) """ def get_derived_key(password, salt, count): key = password + salt for i in range(count): m = hashlib.md5(key) key = m.digest() return (key[:8], key[8:]) def decrypt(msg, password): msg_bytes = base64.b64decode(msg) salt = msg_bytes[:8] enc_text = msg_bytes[8:] (dk, iv) = get_derived_key(password, salt, 1000) crypter = DES.new(dk, DES.MODE_CBC, iv) text = crypter.decrypt(enc_text) # remove the padding at the end, if any return re.sub(r'[\x01-\x08]','',text) 
+1
source

I updated the python script with user3392439 with encryption support. Wish it was helpful.

 import base64 import hashlib import re import os from Crypto.Cipher import DES """ Note about PBEWithMD5AndDES in java crypto library: Encrypt: Generate a salt (random): 8 bytes <start derived key generation> Append salt to the password MD5 Hash it, and hash the result, hash the result ... 1000 times MD5 always gives us a 16 byte hash Final result: first 8 bytes is the "key" and the next is the "initialization vector" (there is something about the first 8 bytes needing to be of odd paraity, therefore the least significant bit needs to be changed to 1 if required. We don't do it, maybe the python crypto library does it for us) <end derived key generation> Pad the input string with 1-8 bytes (note: not 0-7, so we always have padding) so that the result is a multiple of 8 bytes. Padding byte value is same as number of bytes being padded, eg, \x07 if 7 bytes need to be padded. Use the key and iv to encrypt the input string, using DES with CBC mode. Prepend the encrypted value with the salt (needed for decrypting since it is random) Base64 encode it -> this is your result Decrypt: Base64 decode the input message Extract the salt (first 8 bytes). The rest is the encoded text. Use derived key generation as in Encrypt above to get the key and iv Decrypt the encoded text using key and iv Remove padding -> this is your result (I only have implemented decrypt here since that all I needed, but encrypt should be straighforward as well) """ def get_derived_key(password, salt, count): key = password + salt for i in range(count): m = hashlib.md5(key) key = m.digest() return (key[:8], key[8:]) def decrypt(msg, password): msg_bytes = base64.b64decode(msg) salt = msg_bytes[:8] enc_text = msg_bytes[8:] (dk, iv) = get_derived_key(password, salt, 1000) crypter = DES.new(dk, DES.MODE_CBC, iv) text = crypter.decrypt(enc_text) # remove the padding at the end, if any return re.sub(r'[\x01-\x08]','',text) def encrypt(msg, password): salt = os.urandom(8) pad_num = 8 - (len(msg) % 8) for i in range(pad_num): msg += chr(pad_num) (dk, iv) = get_derived_key(password, salt, 1000) crypter = DES.new(dk, DES.MODE_CBC, iv) enc_text = crypter.encrypt(msg) return base64.b64encode(salt + enc_text) def main(): msg = "hello, world" passwd = "mypassword" s = encrypt(msg, passwd) print s print decrypt(s, passwd) if __name__ == "__main__": main() 
+1
source

For @cooljohny

I really don’t remember how this code works, but I'm 99% sure that it is. I wrote it, carefully digging out the specification from the Java implementation of pbewithmd5anddes and testing this python against it. I passed this particular code to the client, and it worked great for them. I changed the constants before inserting them here, but that’s all. You should be able to confirm that it produces the same encrypted output as the Java lib, and then replicates it to ruby. Good luck

 import base64 from Crypto.Cipher import DES from passlib.utils.pbkdf2 import pbkdf1 password = 'xxxxxxx' iterations = 22 salt_bytes = [19,15,78,45,34,90,12,11] # convert saltBytes to a string salt_string = ''.join([chr(a) for a in salt_bytes]) # a sample request raw_data = '''{"something":"to","encrypt":"here"}''' # from the standard... padding_value = (8 - (raw_data.__len__() % 8)) padding_data = chr(padding_value) * padding_value padded_data = raw_data + padding_data # 22 iterations, 16 is the # of bytes in an md5 digest pbkres = pbkdf1(password, salt_string, iterations, 16, 'md5') # split the digest into two 8-byte halves # this gives the DES secret key and initializing vector des_key, iv = pbkres[0:8], pbkres[8:16] # encrypt with DES cipher = DES.new(des_key, DES.MODE_CBC, iv) cmsg = cipher.encrypt(padded_data) # and base64 encode base64.b64encode(cmsg) 
+1
source

Source: https://habr.com/ru/post/1415114/


All Articles