Implementation of PBEWithMD5AndDES in Ruby

跟風遠走 提交于 2019-12-23 13:03:30

问题


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

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

Updated:

I used a gem chilkat to implement it but it is paid, i need an opensource solution.


回答1:


You don't need to actually implement PBEWithMD5andDES assuming ruby has a DES implementation. What you need to implement is the key derivation function ( who you get a key out of a password) and then feed that derived key to DES with the appropriate mode and padding.

Thankfully, the key derivation function is not particularly security critical in implementation, so you can do it yourself safely enough. According to the rfc, PBEwithMD5AndDES is actually the PBKDF1 ( a ker derivation function) used with DES in CBC mode .

PBKDF1 does not look that hard to implement . Looks like you can do it with a for loop and an md5 call.

Note that you may still get some odd results because of the possibility of a different padding scheme being used in Java and Ruby. I assume that the spec one is pkcs 1.5 padding, but at a quick glance, I can't confirm this

5.1 PBKDF1

PBKDF1 applies a hash function, which shall be MD2 [6], MD5 [19] or SHA-1 [18], to derive keys. The length of the derived key is bounded
by the length of the hash function output, which is 16 octets for MD2 and MD5 and 20 octets for SHA-1. PBKDF1 is compatible with the key
derivation process in PKCS #5 v1.5.

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

PBKDF1 (P, S, c, dkLen)

Options: Hash underlying hash function

Input: P password, an octet string S salt, an eight-octet string c iteration count, a positive integer dkLen intended length in octets of derived key, a positive integer, at most 16 for MD2 or MD5 and 20 for SHA-1

Output: DK derived key, a 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:


I know it is super old but I had the same problem and just solved it so here it goes to encrypt, where salt is your salt sting, passkey is your password key string and iterations is 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



回答3:


For what its' worth, I'm posting my python code, which actually works (I have tons of encrypted values which were done 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's 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)



回答4:


I've updated python script from user3392439, with encrypt support. Wish it 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's 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()



回答5:


For @cooljohny

I do not really recall how this code works, but I'm 99% sure it does. I wrote it by carefully digging through the spec from the Java implementation of pbewithmd5anddes, and testing this python against that. I delivered exactly this code to a client, and it worked fine for them. I changed the constants before pasting it here, but that's all. You should be able to confirm that it produces the same encrypted output as the Java lib, and then replicate it in 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)


来源:https://stackoverflow.com/questions/10814071/implementation-of-pbewithmd5anddes-in-ruby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!