New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid unnecessary double string split in ActiveSupport::MessageVerifier#verified #42919
Avoid unnecessary double string split in ActiveSupport::MessageVerifier#verified #42919
Conversation
signed_message.split("--") | ||
end | ||
|
||
def _valid_message?(data, digest) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we no longer have a message here, what do you think about something like:
def _valid_message?(data, digest) | |
def verified_digest?(data, digest) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes absolute sense! With that said, I'm not the biggest fan of the way it reads. It seems to suggest that we already did something with the digest. What do you think of #verifiable_digest?
or #matching_digest?
or #digest_matches_data?
? I must confess that I have a slight preference towards the last.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#digest_matches_data?
looks good to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, it's done! 😉
@jcmfernandes I was looking at a sample message and the digest is always at the end. require 'benchmark/ips'
signed_message = "BAhJIgIAEDQ4UEFlbFZFTmJGcUVJZWJNWnQ3azdwVGp6N0lpdkNTTzdob1kxcDBvZlltbWJYUks2bGVUNnMza2V4U0hRVEVpNkYwclhiWmZnb2k5NTkzVEdqeFFSdlQ2RkE1bmg0RlN0cGgxYzFIM3hOaHRwUXlvcDJHdGZQY3lycHlWa1ZodVI1VkZTUmpvTUN6TEZTcGxaVjV4OEloVzk0OUZMa1lqbm5Oc1gybFNQekJIeEVrOUpnQjUzZHFqRnhhM1FKMENYVUlkQ3Q5NDJ2bFpDNkE5T05ITnAxNTU2YTFjSVpDRjdyU2NpZ21JTmVMV0M5Y1c4Y3hWOXVWQVZZU254b3plYVlTSUt3SEVBVUtROFNVdUVvdXZaM0NuVm1tdWIwQ1MwNnJwQXBwS2FQSmE5Nm5Db0Jhb3dodmR0QW5ucG82cDdNbEJtRzZ6MG5JWEVTV1ZkaFhGRGl2MG1QaFNZR3JsR055clVzNThxelJUbUFPZ21VaTM1TTBDaG82QUxzY0ViQjAxaEZ5WTVzc2pyd3Z6TVlLb3k3ak1jNGVvV1dLZGYwM2tRQjdyZHpLanRqUnRaYzZYTUVVRGRrNUpuWG1kZlI4cEw5NzY5MnkyTFVnQVdFQkJrWTMwTEYyMlFROVhTVGhXU0MxVzkwWWd4OTBVWm5aTEFDMkZ0SjBQc0ZxWFQ5Tk8zOGhmZGZaM3p0bXVBNm5seUNhYkdOTlZFTnFwbmVYMDNFT0UyaEZJUEJZdnFFaDRMSVl5Nk94YlZBajRNd2dIRGZ0WGluaGRudFpyWUFLaXpOQ0ZqcTc5aU1nTTdqcTNWV1ZGcm1VQWdON1dkdnZjVG0wanY5ZWU5bzAydmhkbDVHZURuTDI2VzcyQ1lnc3R6NGl4RDhYV3RRMUxCcXdsMFg4QzlpMkRKUGdYY0VSUkZ6QkhFNDFQRm1NY1ozb0V0bTM2bm8wMkQwSmlWM3UxWnVMaWlLRmVybjFIMmRSeHQ2dzkxM01HVlVaRTVhZDlUb3JuSUpTQUVHb2NQV2R2SWxGcUxMeEE1UGNBSTVJT25yemRhSU1ZRDdxQTNUMVJIenl0RFJxdFlvZTF0QVpXSjM5UTNIUENOdlZOcElPd1lBaHdudWFRd2JKVU5RN3JENmRyUkVpdzdWbEJwSFk4NjhQdzN0d2l6bVdsZnVIT0R3SThhQkxtdUliTjBLOEl4SGM4dUJPTTFjWXkwdFh1QjAwRm5uUmJaVm1tUlpjS2xrQkpCU1poYkVhWGFvZ3cxQVVZalppTVM0TXNMdUNlV2JWN2J5RVFCNWJuQzhlcGJiOEdVaUVPTFdnUTJUSUl6czZ4eEpXZWR0U3NMZk9IbTlzcmhXSGxkeWlNUms5ZWVPZTNyUmhIMDZyc0l0VUtSNW9oRXowTlJVcTExTXg0cDluTmtjMXNxZk9IVWVBcVBCOWVpVnZNdWhudTB1RXYydENramo0Wm5YVnd2V2Y1TmNoR3lWaFdkRzdseEhHaTUzVWxnSGY1M2V3bmliQnRCU00yWHlJaG9YYmQ2Y1F1eGJFOUhaWVJFbHFmUFZBSzJmTG5kY3JsVU5XTVdNNGp1czc3dnZVb0ZtNVNkUkJyVVlmSFN1MkNpb0F0Um50UDNKamxmZDVnUWZER21TS202d21PSEM0S2d4RUh5TXhTdmlNTVZFR1J1UEpPbUFDZ2kyTVYxbms1d3doM3NyR0k5MG5ZdUZ4VUJVdTFPNWxQSUJtYXYyY0EyTXpvOEVLWjVseWlscjNnRlZKbW9ZTDFvZml4Y1ZMQUNxcUFpQkxmaTd4Tk1HQmZSSTlMdDA0TDFOYVpwVHJ4MHlpOWczaE00Q2JuMmlYeUlNMTBJZ1h1YkxlUVJTYlpDeHhnTDZUWGd1bERGOVcya21oMjRlWEl0UmRPWkxXRWNadTBuWkg1T3pWTTBZd25sdEpERldNTG9jWjF3QTJNOFJkSzlmdHpiMTdXWmhnM2RYSkM0QU1rRzBpeXc1bVdjUWVweko0V2hpRnNlVng3QnJJd1ZqZFZTSDRYU1V6R0lnMDZuN2NtVmJ2Z0tDaHhDYlNnZ3JGMHc1blI1Y1hkajNQVVdreFRPYlFsOFBTcFR1S1pIZWp5NVBNVW9zWWdZbVpZaVg2N0VXQk9VbElQNWJhNnB6Vm02UWt3MWNPbzQxUjIxcHFoZ3RWRlU3T3NLekd6YU5lQzFCRnVNYWFONWNNZ01LcnNTUUY1Mm5rUTRUR2dFN0FENTBScXdsS255UHJBVjVBdGwyNEJaV3JWaWFIbTEwSTNPQW1xaE56cXlCelozTHlNb1pvNkZwRjlxUEZSa09zbldGNzFQVUQ5ZGdQQWd3T3FnT2tzaUN2VG0wVUhMenBnUnVVcXZCZW10MjRKTlB5QVhnS0FZV3h6R3ZydXVyemc2cEZhZDI3dWthYWIzckVVUHhxdzdqTU5YSHRWZ05lSnR4cGNMVDB6dTBuTFdGNkROalhXeVNZUEV6Q1YyZ3NXQkRzRHpvellHN3NDUXdtTnB4dExRd21XM3RsQ09MMmZZMVQzU0dQcUZqSzkxMXZOUERhRE5xb3NxTzUzZDlvMVA4ZHJPV3J4WVYzaGxlSWowTUxGbUQ1d0kyQkxJMmxBeHRnMlRWV0hCT1FESGF3allDbDJvRjN3Q05seXZzekltZjk4R3VNMFcyd1p6YWdhQ1NQWE1NSVZSeURWMGpaeHEwam5PR2E0OXhnQTdUM1lHWnBxSlg2ajJlWThhVFVTVFp0TDRzNkJiRUU4ZmxGYUJDQ1hnQ0dIdmdwR1RKSWdXRTVKUGlkc0IwdER2WjgxemF3RFVNUXVLR3NZRFVKNjNta0c2NjlNZEcwZUxxT0lKODU2UkNQeG43VGxyTjBPN0RyM3J3ZVJTSFJOZlc3eVhYbTlLSEZUWjJQSEllaGtOUGNtRTBydFc0MXdXMWRZblRWMnYzZnNtVVZLWFFlckhOY0YxdVJvNFhQdExxeU5iN2JTSXZKa0tpQ2JraEFCY2V1ajlYb1hjZkFzOUVmOWJ4WURYSko4bU1vOFJvdEFZNE9sZ1Q5dmVpVW4xZzNZUUMyUnhtT0NQQkVGd2ZsN2pqTmg5ZDNwNzBCTnk2d1NoeERMM2pwZmhNdVE2VkQ4bE1XdVlKVXNlczdYNFlXN1drTFI5VXpzVmFmV0w3V0d4SWNneWRXU3M0MjlxeDlpMGhVcWRGb2RwNDA5ODFxc0h2YUo3eHhRVW5LQ0M1N05tdlJjeERwdmN0OFhxUTZkMFE3b1F6dGY3VEJaOEd2VldxV2VpWkFDRWliWUZ0ZWljYnB3Y29xcm9VRjdvVnIxcVVuaEwycUFtQVlsZEtzVVBQWHpNN0MxYmtjTTJ0QzVhMlJPdzRrR090Zk5TU09ERE0zaU5EdnE2Y3N2TzB3WTdOcDhqcHJUaDRDekFEM3hhTFM3UkNmVkx6bFEzc1d6NUt3Qko5ZGxRQnZqZVp3MXFDc2F0UnhpZDl2aXY3b0g2U2JoaEpJdVFLaGhleG80bHc1TlI4TmRhc0Z3RXBiTkl6NEZRcE9uS2dnYWNzNDQxbmEyNm9oTXd5SHBZd0JHY09KMEtDYkNMZ0UzTnZwZWxoVFRGRXpPUG5heVV6SmdtcHEybFZsWmV1d1d4dXB4MENHUWRxdjVsNXVVcGxuMHNmelNBaEQxWW9qcFRaRGZtVmFwanNMMm1NM2dyQjZ4Nmh2VUl5c0c1Mk5pRkowT2hLa2lIYlVldE5CU2lieEd5dDN3Q1N6bEhpdnVSQjFOa0lmVmgxT2x6SWh4cGZXdGlZNEdwUHlkdEFQcFZid2NLQUh6WTZxSEdlaWVnamFkaTJweVY4bDBFcWxLM3JVNDVIa3VhWnNjcnhCNjUwUTlJQzNQWnRKOEtRd3pqQk50d1piYlcwcHc2RENQeHpURXJlOEVZNzQ2OXZXSHJtTXlwN0d3S0VxdDJ4bWZTQXNjV0I5TzlaaXBhWk05ZmVNaUtFdjZmdlhwUlFGbVdPcGgycVFaNXhEMjlQRUZHU3daREtGNUJlbVhvUzF2SGVWZE9iSU1EbldIUk1RY1VkRWZLa3RMOUpRVkk3WXRlUUhnTENrNTU1MThET2tYY0FaNUpFS0w0aEc4eWlGQWZGdWljeHZuaE45ZnZHZXhwbENzY1ZmTGdSS0pTMlZ3c203MHpsT1BZSXowN0FUNDJuRGdBQzZkZ2ZnbHBlTnE2bHI1Wm1lTnpjMTE2UzB4RXFhMHFTcmljdW1HVFIwTVpzWWV1emFyTEV2OFpsS3QzUzhEQmdSNG40QlJNWHZVbkpyT2Y2aUJZMmVFcTRoTEFYTDVVQjVCVFpQYzRNdWVISmRMWjBSNEFqU0NLUmxWamJnbjdaVUZKcTRwRUtjZVlCNWpUTmZPU2Q2OUxlZjdERVZnSzlOUjZqelJ4ZHRmdU10cmFsYlFYbTVKeUhUbzh5VmFkWHM2V2lRZUxaR0pUenhiM2ZJdm5SbWgybnN5cHkwc0I4Z0dMeVNNQVQ4R2xxc3BVbjZ4R1N2bHlPV0JNajBJV0hCaTRZd2NpaWZ0eXhyN0syamVpMEdxb1RURlRwYTRLNVhBQ3dhd0JwSzYwZXViek95SkR4TFFTT090MXA3UnJWWnJISFozSVZuU1Ywc1N2Uk1rU0lBNmNwNzhLcFFnNzVQM3NKWnZucmhWamphRjNqWFE2aDB5cWVoZVE3dWJlWGhmQmdFVHNsbHRaRFg2bXZwTkJKUUUwREthSWJ6QmFLaFlJQnBncXBhd1p0cnJRUGY4YVZwa0RIODU3VU8zZXpWanVBVE0zVDNRcTJqZ1BZNW1GSHlwUGlpTzJSc2ZYQU9FQndZRW94RHpwM1o2NkZQMEEzYzhRaDZ4VnVOdkdxQXdQSFI1YlhWY3ZHUndoYXRieTRydXNzSmUwNDc3SDZKVVY0UW9WeHBPQUxmQTdVT3BSN3FDQUdDcXpUSXRFUDhleVNFZWRpcVhyeUh1OGo0N2UwUUJkTzI4MFBQM2J5aXdiSmRmcXY3bmRFNFRIQk5QNjBXbTBlY0ZUbVI0M21Gcm8yMVM1emV2b0ZJWm5mQURNRzNVcHdZemFoYUFMdFFsQmUwQUV5Z21xeE9XQllDNWUxRjNUUXNPb1ZKbW9SWERxVlVzaWxrZGFaMW9pTVdhNFVTNTlZdnBIREpSZ1RXSEFKbDNVVjZWYlZrc0FDWnc2YXFCeEdPUkxEdW8wMDBlbkI4aGlXMHBKMlZESHlCNzJPOVVSWmFscktUZFVRb0pBT2EyQlQ0SU9HWnVoSFRNbmN1NzREbWpHUFFGcWJNYU9Da1RDc3JMZmZMVWxyS3dWZDBTVXVTU1lJZ3J5enlUVkdCc2lDWkRObmE3Um5QUlVQSTJ5QU9sYU54b3JHbnlWeTJLeDhFQkVDZDVmbjJ6bnBwWmxJYlB6RTczbUF5Y3J4b1VnamIGOgZFRg==--60ae7b3ca695673426771dfb5e817d43718f3b92"
GC.start
Benchmark.ips do |x|
x.time = 10
x.warmup = 2
x.report('split') do
signed_message.split('==', 2)
end
x.report('reverse split') do
signed_message.reverse.split('==', 2).map(&:reverse)
end
x.report('split with regexp') do
signed_message.split(/(.+)--([^-]+)$/)
end
x.report('rindex') do
index = signed_message.rindex("==")
signed_message[0, index]
signed_message[index+2, signed_message.length]
end
x.report('rindex with ranges') do
index = signed_message.rindex("==")
signed_message[0...index]
signed_message[index+2..-1]
end
x.compare!
end
|
You're absolutely right @p8! In fact, I believe we can do better. We know what digest is being used, so we can know how many chars can it take to be represented in base64 (worst case). That means that we can use EDIT: I was forgetting that the HMAC is being represented in hex but the logic is the same. |
return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.empty? | ||
|
||
separator_index = separator_index_for(signed_message) | ||
return unless signed_message[separator_index, SEPARATOR_LENGTH] == SEPARATOR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we check for the presence of the separator. Worth extracting to a method?
First, ignore the ugly commit history as I'll fix that before we merge. What the benchmarks show is the much-increased resistance to invalid messages, those that don't feature the separator. The new code is not sensitive to the size of the input whenever it doesn't feature the separator. Using So, we are showing a ~1.4x speedup for 4 kB valid messages and a ~1.8x speedup for 4 MB valid messages. When dealing with invalid messages, we are looking at ~3x to ~10x speedup for 4 kB messages and over 1000x speedup for 4 MB messages. I dropped a few comments in the PR and would appreciate your feedback @p8. Thank you! 🙌
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
Hey @p8 any changes I should make here to get this over the line? |
22f44f5
to
d9558e7
Compare
ActiveSupport::MessageVerifier#verified is causing signed_message to be split twice: first inside #valid_message? and then inside #verified. This is ultimately unnecessary. We can avoid String#split all together by calculating the indexes of the data and the digest in the payload. This increases the resistance of the solution against ill-formed payloads that don't include the separator.
d9558e7
to
02eda1b
Compare
…-message-verifier Avoid unnecessary double string split in ActiveSupport::MessageVerifier#verified
Nice work @jcmfernandes ! |
Thank you for your help @p8! 🙌 |
We can avoid using String#split by calculating the indexes of the encrypted data, IV, and auth tag in the payload. This increases the resistance of the solution against ill-formed payloads that don't include the separator. This is a follow up to the work in PR rails#42919.
Summary
ActiveSupport::MessageVerifier#verified
is causingsigned_message
to be split twice: first inside#valid_message?
and then inside#verified
. This is ultimately unnecessary.Benchmark
I renamed the current implementation of
#valid_message?
and#verified
to#valid_message_old?
and#verified_old
respectively, with the latter modified to call#valid_message_old?
. I then ran the following benchmark:With
SIZE = 2**12
(4 kB, approximately the maximum size of a cookie):With
SIZE = 2**22
(4 MB):As expected, with a larger signed message, a greater speedup.
Other Information
None.