Redsys HMAC SHA256 migration using Ruby

Last week Redsys announced the “HMAC SHA256 migration” that is that they will change their signature algorithm and also the message structure. As usual they made available two libraries in PHP and Java in order to adapt your projects easily, but what if your application is made with Ruby on Rails?

We have implemented the new signature algorithm in Ruby.

Firstly, let’s explain the new requirements. The new POST data request only has 3 paramenters:

  • Ds_SignatureVersion, with a constant value “HMAC_SHA256_V1”
  • Ds_MerchantParameters, containing the old Ds_MerchantXXX structure, but encoding it in Base64.
  • Ds_Signature, the new signature that is made in two steps.

So we can partially reuse our old Ds_MerchantXXX structure:

  def merchant_parameters_hash
    {
      "Ds_Merchant_Amount" => 4999,
      "Ds_Merchant_Currency" => 978,
      "Ds_Merchant_ProductDescription" => "Jacket",
      "Ds_Merchant_MerchantCode" => 34512345,
      "Ds_Merchant_MerchantURL" => "https://www.example.com/callback/",
      "Ds_Merchant_UrlOK" => "https://www.example.com/payment/ok/",
      "Ds_Merchant_UrlKO" => "https://www.example.com/payment/ko/",
      "Ds_Merchant_MerchantName" => "ACME",
      "Ds_Merchant_Terminal" => 3,
      "Ds_Merchant_TransactionType" => 0,
      "Ds_Merchant_Order" => "12345"
    }
  end

With our parameter hash we can construct the new message

  def signed_request_order(merchant_parameters)
    encoded_parameters = encode_parameters(merchant_parameters)
    signature = calculate_signature(encoded_parameters, merchant_parameters["Ds_Merchant_Order"])
    {
      "Ds_SignatureVersion" => "HMAC_SHA256_V1",
      "Ds_MerchantParameters" => encoded_parameters,
      "Ds_Signature" => signature
    }
  end

To encode the parameters hash we have to transform to JSON and then to strict Base64 (without break lines, because this data will be included in a input field)

  def encode_parameters(parameters)
     Base64.strict_encode64(parameters.to_json)
  end

Now, we have to calculate the signature. It is required to create a unique key for every request, based on the order id and in the shared secret (get the secret in your Redsys “canales” administration console). This unique key is Triple DES ciphered.
Finally, using this unique key we have to sign the merchant data using a HMAC SHA256 algorithm and to encode the result using Base64:

  def calculate_signature(b64_parameters, ds_order)
    unique_key_per_order = encrypt_3DES(ds_order, Base64.decode64(SHARED_SECRET))
    sign_hmac256(b64_parameters, unique_key_per_order)
  end

  def encrypt_3DES(data, key)
    cipher = OpenSSL::Cipher::Cipher.new("DES-EDE3-CBC")
    cipher.encrypt
    cipher.key = key
    if (data.bytesize % 8) > 0
      data += "\0" * (8 - data.bytesize % 8)
    end
    cipher.update(data)
  end

  def sign_hmac256(data, key)
    Base64.strict_encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, data))
  end

That’s all for the request. Now we have a valid redsys message, we have to create the POST request form and submit the data in 3 input fields.

To manage a Redsys response we have to decode the message and validate the signature. The response uses the same message format, so we will get a POST request in our callback method:

  def callback
    merchant_parameters = redsys_response_parameters
    check_response_signature(merchant_parameters['Ds_Order'])
    do_whatever_with merchant_parameters
    render text: "", status: 200
  end

To check the signature we need to extract the order ID, so first we need to decode the Ds_MerchantParamenters data:

  def redsys_response_parameters
    decode_parameters(params["Ds_MerchantParameters"])
  end

  def decode_parameters(parameters)
     JSON.parse(Base64.decode64(parameters.tr("-_", "+/")))
  end

To check if the signature is valid we need to regenerate the signature with the order id

  def check_response_signature(order_id)
    response_signature = Base64.strict_encode64(Base64.urlsafe_decode64(params["Ds_Signature"]))
    raise InvalidSignatureError unless response_signature == calculate_signature(params["Ds_MerchantParameters"], order_id)
  end

Becareful with the response signature. It is urlsafe enconded! So we transform it to our strict_base64 format.
If the signature is invalid it will raise an Exception, in other case you can process the response, check whether the result is ok or not and do whatever your application does.

Share this:

6 Replies to “Redsys HMAC SHA256 migration using Ruby”

  1. Thank you very much for your post. It’s helped me a lot. I didn’t take into account the key size of 3DES algorithm to be multiple of 8 and not to append the cipher.final

    • Hello Jose,

      I will have some time next Wednesday, I will have a look at it.

      Thank you.

  2. Thank you very much for this post. I was desperate to Redsys technical department , who did not know Ruby

  3. Thank you for this post. I’m missing some detail like the 3DES key to be multiple of 8.

Leave a Reply

Your email address will not be published. Required fields are marked *