1

Stripe recommends using their libraries for validating the signature, but since they do not have one that will work with Classic, I have to do it manually, and I am really struggling with it.

(https://stripe.com/docs/webhooks/signatures#verify-manually)

Step 1: Extract the timestamp and signatures from the header Split the header, using the , character as the separator, to get a list of elements. Then split each element, using the = character as the separator, to get a prefix and value pair. The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature(s). You can discard all other elements.

Step 2: Prepare the signed_payload string You achieve this by concatenating: The timestamp (as a string) The character . The actual JSON payload (i.e., the request’s body)

Step 3: Determine the expected signature Compute an HMAC with the SHA256 hash function. Use the endpoint’s signing secret as the key, and use the signed_payload string as the message.

Step 4: Compare signatures Compare the signature(s) in the header to the expected signature. If a signature matches, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.To protect against timing attacks, use a constant-time string comparison to compare the expected signature to each of the received signatures.

I think I am on the right track with the following code:

'Keys     
If strStripeMode = "live" Then 
    strSigningSecret = ""
Else
    strSigningSecret = "whsec_CIXV2............................UR8Ta0" 
End if

'1. Payload
    lngBytesCount = Request.TotalBytes
    strPayload = BytesToStr(Request.BinaryRead(lngBytesCount))

'2. Stripe-Signature 
    strStripeSignature = Request.ServerVariables("HTTP_STRIPE_SIGNATURE")
    arrStripeSignature = Split(strStripeSignature,",")
    intTimeStamp = REPLACE(arrStripeSignature(0),"t=","")
    strV1Signature = REPLACE(arrStripeSignature(1),"v1=","")
    strSignedPayload = intTimeStamp & "." & strV1Signature & strPayload

'3. Hash using 'signing secret' as the key. 
    set crypt = Server.CreateObject("Chilkat_9_5_0.Crypt2")
    crypt.HashAlgorithm = "sha256"
    crypt.EncodingMode = "hex"
    crypt.SetHmacKeyEncoded strSigningSecret,"ascii"
    strHashedSignature = crypt.HmacStringENC(strSignedPayload)
    set crypt = nothing


'4. Compare the values
    If strHashedSignature = strV1Signature Then 
        'Valid
        Response.Status = "200"
        Response.Write(Response.Status)
        '------------ DO SOMETHING------------
        Response.End
    Else
        'Invalid
        Response.Status = "300" 
        Response.Write(Response.Status)
        '------------ DO SOMETHING------------
        Response.End                                    
    End if




' Bytes to String Function
Function BytesToStr(bytes)
    Dim Stream
    Set Stream = Server.CreateObject("Adodb.Stream")
        Stream.Type = 1 'adTypeBinary
        Stream.Open
        Stream.Write bytes
        Stream.Position = 0
        Stream.Type = 2 'adTypeText
        Stream.Charset = "iso-8859-1"
        BytesToStr = Stream.ReadText
        Stream.Close
    Set Stream = Nothing
End Function

The first issue that I have is that I am not sure if I am generating the signed_payload string in step #2 correctly. The second issue is that the strHashedSignature in step #3 is coming out blank, likely due to not generating the signed_payload string (strSignedPayload) correctly.

user692942
  • 16,398
  • 7
  • 76
  • 175
Digital Fusion
  • 165
  • 4
  • 19
  • You're making an assumption about the `v1` string, blindly attempting to replace it assuming it is always element two in the inital split but the documentation says it can also include a `v0` or more schemes. You start off correct by splitting based on `,` but you should really either split each array element again using `=` or do something like an `InStr()` check for `v1=`. Need to make sure you are combining the right values first before even considering the hash is computed wrongly. – user692942 Aug 16 '18 at 20:16
  • The thing to remember about the `Replace()` function is if it doesn't find `t=` or `v1=` it will just return the original string unchanged. Which then means you are combining the wrong values and the hash will reflect that. – user692942 Aug 16 '18 at 20:24
  • 1
    One thing to note is that Stripe encodes strings in UTF-8, so you should use that as your `Stream.Charset` instead(otherwise you may not read the same string that Stripe sent, which would result in the signatures not matching) – karllekko Aug 17 '18 at 09:08
  • 1
    @karllekko is that documented somewhere, because I did look and there is no mention of what encoding to use when creating the hash? – user692942 Aug 17 '18 at 11:55
  • 2
    Lankymart good point, I don't think it's explicitly said anywhere, but it is true. If you look at some of Stripe's examples of decoding webhook events, they always use UTF-8 e.g. https://github.com/stripe/stripe-python/blob/master/examples/webhooks.py#L16-L33 – karllekko Aug 17 '18 at 14:11

1 Answers1

1

I figured I should update this in case someone else encounters issues in the future.The solution was two-fold.

First is that I completely spaced on the signed_payload string, including the v1Signature for some reason (I'm going with the excuse that it was the end of the day on a Friday :)

strSignedPayload = intTimeStamp & "." & strV1Signature & strPayload

Its only supposed to be:

strSignedPayload = intTimeStamp & "." & strPayload

The second is as @karllekko stated, the encoding needed to be UTF-8

Digital Fusion
  • 165
  • 4
  • 19