Ruby - digest authentication for soap and byte arrays - ruby

How do I convert string or a value to a byte array?
I need it for SOAP authentication. My requirement is to -
On the client side, this is how to create a digest:
1. Client Side Digest Array = byte array nonce + byte array UTC date-time of UTF-8 string + byte array UTF-8 plain text password (concatenate these three).
2. Client Side SHA-1 Digest = Hash with SHA-1 algorithm the Client Side Digest Array.
3. Client Side WS-Security Digest = 64-bit encode the Client Side SHA-1 Digest
Password_Digest = Base64 ( SHA-1 ( nonce + timestamp + password ) )
This is the code I am using to generate nonce, timestamp and digest_password. User password is a string. Some ting is wrong in the whole process and my digest is not successfully generated. I guess I have these data types right, byte array and UTF8 is confusing me.
I added utf8 conversion but no difference.
def nonce
chars = ("a".."z").to_a + ("1".."9").to_a + ("A".."Z").to_a
#nonce = Array.new(20, '').collect{chars[rand(chars.size)]}.join
end
def timestamp
t = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%LZ")
#timestamp = t.to_s
end
def digest_password
ic = Iconv.new('UTF-8//IGNORE', 'US-ASCII')
$time = ic.iconv( timestamp + ' ')[0..-2]
$pass = ic.iconv( password + ' ')[0..-2]
temp = (nonce.bytes.to_a + $time.bytes.to_a + $pass.bytes.to_a)
#digest_password = Base64.strict_encode64(Digest::SHA1.hexdigest(temp.to_s))
### temp = Digest::SHA1.hexdigest(nonce + timestamp + password) ##old
####digest_password = Base64.encode64(temp) ##old
end
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wsdl="http://xml.myserver.com/ok/service/v1_5" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header>
<wsse:Security env:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-1" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>user</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">YWIwM2QyZWI3YTEwMTAzZmNkNmZiNmEwMjg1ODlkOTU0OTNmNmUxYQ==
</wsse:Password>
<wsse:Nonce>ZEUyQ2J6bmw5cjdDZmt1QjVqTjQ=</wsse:Nonce>
<wsu:Created>2012-03-27T11:08:35.125Z</wsu:Created>
</wsse:UsernameToken>

Finally was able to solve this issue.
Password_Digest = Base64 ( SHA-1 ( nonce + create + password) )
nonce = nonce as string. Before Base64 endoce, E.g "1234"
create = time as string. No encoding
password = password as string. No encoding.
Base64Nonce = Base64.encode64(nonce).strip #Base64 encode of "1234"
chars = ("a".."z").to_a + ("1".."9").to_a + ("A".."Z").to_a
nonce = Array.new(20, '').collect{chars[rand(chars.size)]}.join
t = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%LZ")
$time = t
$pass = p
Base64Nonce = Base64.encode64(nonce).strip
$digest_pass = Base64.encode64(Digest::SHA1.digest(nonce + $time + $pass)).strip
"wsse:Username" => username,
"wsse:Password" => $digest_pass,
"wsse:Nonce" => Base64Nonce,
"wsu:Created" => $time,

Is it right to Base64.encode in the nonce method and then Base64.encode again in the digest_password method? Seems strange for me. Maybe you should do
def nonce
chars = ("a".."z").to_a + ("1".."9").to_a + ("A".."Z").to_a
#nonce = Array.new(20, '').collect{chars[rand(chars.size)]}.join
end

Related

Ruby openssl encryption with DES-CBC incorrect result

I am trying to replicate the encryption result from here in Ruby using OpenSSL: https://emvlab.org/descalc/?key=18074F7ADD44C903&iv=18074F7ADD44C903&input=4E5A56564F4C563230313641454E5300&mode=cbc&action=Encrypt&output=25C843BA5C043FFFB50F76E43A211F8D
Original string = "NZVVOLV2016AENS"
String converted to hexadecimal = "4e5a56564f4c563230313641454e53"
iv = "18074F7ADD44C903"
key = "18074F7ADD44C903"
Expected result = "9B699B4C59F1444E8D37806FA9D15F81"
Here is my ruby code:
require 'openssl'
require "base64"
include Base64
iv = "08074F7ADD44C903"
cipher = "08074F7ADD44C903"
def encode(string)
puts "Attempting encryption - Input: #{string}"
encrypt = OpenSSL::Cipher.new('DES-CBC')
encrypt.encrypt
encrypt.key = ["18074F7ADD44C903"].pack('H*') #.scan(/../).map{|b|b.hex}.pack('c*')
encrypt.iv = ["18074F7ADD44C903"].pack('H*')
result = encrypt.update(string) + encrypt.final
puts "Raw output: #{result.inspect}"
unpacked = result.unpack('H*')[0]
puts "Encrypted key is: #{unpacked}"
puts "Encrypted Should be: 9B699B4C59F1444E8D37806FA9D15F81"
return unpacked
end
res = encode("NZVVOLV2016AENS")
Output:
Encrypted key is: 9b699b4c59f1444ea723ab91e89c023a
Encrypted Should be: 9B699B4C59F1444E8D37806FA9D15F81
Interestingly, the first half of the result is correct, and the last 16 digits are incorrect.
The web site uses Zero padding by default, while the Ruby code uses PKCS#7 padding by default.
Ruby does not seem to support Zero padding, so disable the default padding and implement Zero padding yourself.
Zero padding pads to the next full block size with 0x00 values. The block size for DES is 8 bytes. If the last block of the plaintext is already filled, no padding is done:
def zeroPad(string, blocksize)
len = string.bytes.length
padLen = (blocksize - len % blocksize) % blocksize
string += "\0" * padLen
return string
end
In the encode() function (which should better be called encrypt() function) the following lines must be added before encryption:
encrypt.padding = 0 # disable PKCS#7 padding
string = zeroPad(string, 8) # enable Zero padding
The modified Ruby code then gives the same ciphertext as the web site.
Note that DES is insecure, also it' s insecure to use the key as IV (as well as a static IV). Furthermore, Zero padding is unreliable in contrast to PKCS#7 padding.

Encrypt data with DES ECB in Ruby

I am implementing an encryption in a project that I has in another java project.
The code in java project is this:
public static String cifraDES(String chave, String dado) throws Exception {
DESKeySpec keySpec = new DESKeySpec(hexStringToByteArray(chave));
SecretKeyFactory kf = SecretKeyFactory.getInstance("DES");
SecretKey passwordKey = kf.generateSecret(keySpec);
Cipher c = Cipher.getInstance("DES");
c = Cipher.getInstance("DES/ECB/NoPadding");
c.init(Cipher.ENCRYPT_MODE, passwordKey);
return bytesToHex(c.doFinal(hexStringToByteArray(dado)));
}
In Ruby project i want implement this encrypt too. But this dont work:
dado = "53495A45303030386E6F7661313031305858585858585858"
chave = "3455189635541968"
des = OpenSSL::Cipher.new('des-ecb').encrypt
des.key = chave
s = des.update(dado) + des.final
Base64.encode64(s).gsub(/\n/, "")
In terminal I recive this message:
'key' be must 8 bytes
And i need this return: b42e3dbfffd4bb5487a27fd702f079e287e6325767bfdd20
View:
http://des.online-domain-tools.com/link/1145159gOjlrPNRkaT/
You haven’t converted the key and data from hex strings, you can do that using pack:
dado = ["53495A45303030386E6F7661313031305858585858585858"].pack('H*')
(When you do this to the key, it is converted from 16 hexidecimal characters to 8 bytes, so not doing this step is causing the error are getting).
You haven’t specified no padding:
des.padding = 0
And you want the result hex encoded, not base 64. You can use unpack:
puts s.unpack('H*')[0]
Putting it all together:
dado = ["53495A45303030386E6F7661313031305858585858585858"].pack('H*')
chave = ["3455189635541968"].pack('H*')
des = OpenSSL::Cipher.new('des-ecb').encrypt
des.key = chave
des.padding = 0
s = des.update(dado) + des.final
puts s.unpack('H*')[0]
Result is b42e3dbfffd4bb5487a27fd702f079e287e6325767bfdd20.
The error seems pretty clear to me. The key you're using chave is 16 bytes. Your key has to be 8 bytes. So reduce the length of the key to 8 chars then try.

Creating PEM-format cert from N and e in Ruby

I'm dealing with a web service that sends a public key down to the client in the following format.
DB2F71B3B998D600946FD47636122256FB9FA10A33A66707133A73CC458B9D90A593C32DD054BCF5CEA430CE512D659D689BD3E61ECCD4B18B3921FF7FB81F0433B9CE995A3DC096A8DD5C3A5F8EC6EB4C0CF1036CDBA0E29F20B54E1F690002A01F29A7BB9622B05835C23EBDF8F1A0D4581C9579B29877F1457053B681DA72A0DD4BF1133B857BEB7C3971416F12D6630F7939DF3C44DFB4555B5A8260134FC3AEB328CF76697367A336E881FE291F860E5E7BE708F9BA7C046632868B17468AC7BD8013032F17BDA9DF9DFF0552B33C8431075BA0936BDFF9E6173EF1901AFE27FD72422EAD6F77059F15BADB4F376F56C1F04D6DB52E509954399DEC28D9:10001
Note the N and e values are separated by a colon ':'.
I would like to use OpenSSL::PKey::RSA in order to work with the public key, but it only accepts DER and PEM format certs. How can I format the public key I am given into a PEM or DER format cert that is consumable by the OpenSSL wrapper library in Ruby?
EDIT:
This python code does essentially what I am attempting to do in Ruby. I'm looking for the equivalent of the RSAPublicNumbers class.
https://github.com/jpf/okta-jwks-to-pem/blob/master/jwks_to_pem.py#L40
Check base64_to_long
def generate_key_from_jwks(kid_header)
key = OpenSSL::PKey::RSA.new
exponent = kid_header['e']
modulus = kid_header['n']
key.set_key(base64_to_long(modulus), base64_to_long(exponent), nil)
end
def base64_to_long(data)
decoded_with_padding = Base64.urlsafe_decode64(data) + Base64.decode64('==')
decoded_with_padding.to_s.unpack('C*').map do |byte|
to_hex(byte)
end.join.to_i(16)
end
def to_hex(int)
int < 16 ? '0' + int.to_s(16) : int.to_s(16)
end

How to generate signature using SHA-1 HMAC for google map in ruby

I am trying to generate signature using SHA-1 HMAC in ruby for google maps calls. I have got a python's code from the internet which I am trying to copy into ruby. Following is phython's code
import urllib.parse
import base64
import hashlib
import hmac
GOOGLEAPIS_URL = 'http://maps.googleapis.com'
STREETVIEW_ENDPOINT = '/maps/api/streetview'
encodedParams = urllib.parse.urlencode({'size':'50x50','location':'42.35878993272469,-71.05793081223965','sensor':'false','client':'gme-securealert'});
privateKey = 'Encoded_Key' # e.g XEL-B9Zs3lRLajIXkD-bqTYix20=
decodedKey = base64.urlsafe_b64decode(privateKey)
urlToSign = STREETVIEW_ENDPOINT + '?' + encodedParams
print(urlToSign)
signature = hmac.new(decodedKey, urlToSign.encode('utf-8') , hashlib.sha1)
encodedSignature = base64.urlsafe_b64encode(signature.digest())
print(encodedSignature
)
that generates OI2DXDLq7Qd790Lokaxgqtis_pE= signature.
Following is the Ruby code that I am trying to achieve same signature with.
GOOGLE_APIS_URL= "http://maps.googleapis.com"
key = Encoded_Key # e.g XEL-B9Zs3lRLajIXkD-bqTYix20=
data ='/maps/api/streetview?size=50x50&sensor=false&client=gme-securealert&location=42.35878993272469,-71.05793081223965'
data_array = data.split("?")
STREET_VIEW_ENDPOINT = data_array[0]
query_string = data_array[1]
encoded_query_string = URI.escape(query_string) # to encode parameters only
decoded_key = Base64.decode64(key) # to decode the key
data = STREET_VIEW_ENDPOINT << '?' << encoded_query_string
#p "DATA #{data}"
#data = Base64.decode64(data)
#puts "data #{data}"
digest = OpenSSL::Digest.new('sha1')
p OpenSSL::HMAC.digest(digest, decoded_key, data)
hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, decoded_key, data))
p hmac
but this seems to be not working for me.
Please guide.
You can create the hash for request parameters and use URI.encode_www_form for encoding parameters. Use Base64.urlsafe_decode64 and Base64.urlsafe_encode64 instead of Base64.decode64 and Base64.encode64. In my case, I have used reverse geocoding api. You will need to ammend the parameters and REVERSE_GEOCODING_ENDPOINT_JSON. Hope this will help you. Please let me know if you have any queries.
GOOGLE_MAPS_API_CLIENT_ID = 'gme-xyz'
GOOGLE_MAPS_API_CRYPTO_KEY = 'private_key'
GOOGLEAPIS_HTTP_URL = 'http://maps.googleapis.com'
REVERSE_GEOCODING_ENDPOINT_JSON = '/maps/api/geocode/json'
str_latlng = lat.to_s + ',' + lon.to_s
encoded_params = URI.encode_www_form({'latlng' => str_latlng,
'client' => GOOGLE_MAPS_API_CLIENT_ID})
decoded_key = Base64.urlsafe_decode64(GOOGLE_MAPS_API_CRYPTO_KEY)
url_to_sign = REVERSE_GEOCODING_ENDPOINT_JSON + '?' + encoded_params
digest = OpenSSL::Digest.new('sha1')
signature = OpenSSL::HMAC.digest(digest, decoded_key, url_to_sign)
encoded_signature = Base64.urlsafe_encode64(signature)
signed_url = GOOGLEAPIS_HTTP_URL + REVERSE_GEOCODING_ENDPOINT_JSON + '?' + encoded_params + '&signature='+encoded_signature

Encrypting/decrypting 3DES in Ruby

I have a key.bin file which content is something along the lines of:
-12, 110, 93, 14, -48, ...
This is being used by a service to decrypt 3DES content, but I need to encrypt it via Ruby.
I've tried loads of scenarios with how to set the key and what to do with it, but to no avail as of yet:
Tried splitting the key by , and converting each number to hex, concatenating the hex values to make the key
Tried converting the number string to binary
Tried converting the resulting hex to binary
I assume what I need to do is something simple like:
des = OpenSSL::Cipher::Cipher.new('des3')
des.decrypt
des.key = mistery # this step is where i'm having problems at
final = des.update(encrypted) + des.final
Any ideas on what I should do with this key?
Key sample:
-62,-53,124,-110,37,-88,-48,31,-57,93,70,-101,44,69,-88,-57,-123,-99,118,-119,110,55,11,14
Data sample:
NEb2b9sYXgod6mTvaRv+MRsTJvIiTTI9VjnDGcxjxcN5qBH7FXvxYI6Oj16FeKKsoQvjAmdju2SQ
ifJqPJTl97xeEbjdwm+W8XJnWs99ku85EznVBhzQxI1H2jPiZIcrqIVCWdd/OQun7AjK4w2+5yb7
DPN2OiCIEOz2zK6skJrBw3oTEHpXrSEhydOYxqI+c5hC4z3k5nktN6WSVLIo8EAjwenHPMDxboWF
ET8R+QM5EznVBhzQxI1H2jPiZIcrqIVCWdd/OQun7AjK4w2+5yb7DPN2OiCIFqk4LRwEVq16jvKE
vjz6T4/G34kx6CEx/JdZ1LdvxC3xYQIcwS0wVnmtxorFm4q5QQFHzNKQ5chrGZzDkFzAogsZ2l2B
vcvlwgajNGHmxuVU83Ldn1e5rin7QqpjASqeDGFQHkiSbp4x6axVce2OGgfFpZdzCM7y6jLPpOlX
LOC/Bs1vTwMzcBNRB/Fo4nsX9i4It8Spm228XQNUpQe4i9QGe/4AyKIhHoM8DkXwPZ6rWp0W0UMe
keXjg41cED1JwjAAQSP3RYITB78bu+CEZKPOt2tQ2BvSw55mnFcvjIAYVQxCHliQ4PwgceHrnsZz
5aagC0QJ3oOKw9O0dlkVE3IM6KTBMcuZOZF19nCqxMFacQoDxjJY8tOJoN0Fe4Boz2FPyuggfLz9
yhljVJhxqOlTd8eA34Ex8SdC+5NDByAMumjzcPcXL8YVpSN85gytfd+skXhz3npmJ0dmZZOouu0Z
vMmlaCqw96Sy0L1mHLKbjqmZ/W57OBNRB/Fo4nsX9i4It8Spm228XQNUpQe4i9QGe/4AyKIhHoM8
DkXwPZ5tXdq1dRG6IaS51oNzFFlOoP3wTJuOTpj+zQOBMMOi4ENFyyEwYbG/qE+uY8rVwBOUHv9b
Yd9byvOZbnHDuf4oaWRZ+4K3s2NkEblDF9wU6Mb0ZqnLEJsypjrorH1cNIodIDu8nME1nD5bIDF6
XNrWC6pk6AV6eYQvNJw2QDz0RBD15fz/fAXCvbaCLDnhBKpLXrRbQdV+jxx2ipeC2ceMLLRFRPuR
B+ycYht65lWh4jNjoEsBXGFKiT0bSX6Lx/ZQD3twJWbML8ifRhw7SW0jOkUF+dAfXYNaD6nqA6Xq
TkcsDGaJsVq8wwCIWNh6tDRSw7ba4c391147kmnqEgXdKmmnEzUfHtpRw88C0/u0qj809hB4qB0B
lxj/87aDo4VOz9S4jjtk849CxtA/a9+532A4YlXjsPt/f0KZ2drAGEr1VSWzaLh/sMwP5tznmPaK
uozS6C74gMNdhtNMFz0HONcYecS0hg4lrdRyljROgzC33QoBIHbQXJrG0OXE3+81uhJwusEnFaD9
8Eybjk6YeNk3oxL3C5fx/xXgFmhcLLGdxRe/am0jqA1gV6MyQFUKtzdnNOUYpHkYXT9Ea7YYln4Q
D96Z9AI5EznVBhzQxI1H2jPiZIcrqIVCWdd/OQun7AjK4w2+5yb7DPN2OiCIFqk4LRwEVq16jvKE
vjz6T4/G34kx6CEx/JdZ1LdvxC3iEcYTrEH9kKhPrmPK1cATlB7/W2HfW8rzmW5xw7n+KGlkWfuC
t7NjZBG5QxfcFOjG9GapyxCbMqY66Kx9XDSKHSA7vJzBNZw+WyAxelza1guqZOgFenmElSgtUOo7
TEunuphaMIEQgo0udojG6dm2FtRmA4yntNCnCDzGTY72nrFBz3EZmVXGEm6X3Xd5Ito=
Got it working!
Here's how:
Decryption
def read_key(key_file)
File.read(key_file).split(',').map { |x| x.to_i }.pack('c*')
end
des = OpenSSL::Cipher::Cipher.new('des-ede3')
des.decrypt
des.key = read_key('key.bin')
result = des.update(decoded) + des.final
Encryption
def read_key(key_file)
File.read(key_file).split(',').map { |x| x.to_i }.pack('c*')
end
des2 = OpenSSL::Cipher::Cipher.new('des-ede3')
des2.encrypt
des2.key = read_key('key.bin')
result = des2.update(result) + des2.final
puts Base64.encode64(result)

Resources