Skip to content

Commit 6378558

Browse files
committed
⚡️ Memoize salted_password for AUTHENTICATE SCRAM
`SASL::ScramAlgorithm` and `SASL::ScramAuthenticator` use `salted_password` _at least_ twice: once to compute `client_key` and once to compute `server_key`. It is actually used more than that, since `client_key` and `server_key` are also used multiple times each. Computing `salted_password` is _intentionally_ computationally expensive, so it should be cached. Although `client_key` and `server_key` are far less computationally expensive, they _are_ used multiple times, so they are memoized too. Ultimately, we _could_ memoize most of the methods in `ScramAlgorithm`, but I've decided to keep it simple by only memoizing these three.
1 parent 07b6dd3 commit 6378558

1 file changed

Lines changed: 19 additions & 0 deletions

File tree

lib/net/imap/sasl/scram_authenticator.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ def initialize(username_arg = nil, password_arg = nil,
100100
@server_first_message = @snonce = @salt = @iterations = nil
101101
@server_error = nil
102102

103+
# Memoized after @salt and @iterations have been sent.
104+
@salted_password = @client_key = @server_key = nil
105+
103106
# These values are created and cached in response to server challenges
104107
@client_first_message_bare = nil
105108
@client_final_message_without_proof = nil
@@ -155,6 +158,15 @@ def initialize(username_arg = nil, password_arg = nil,
155158
# Net::IMAP::NoResponseError.
156159
attr_reader :server_error
157160

161+
# Memoized ScramAlgorithm#salted_password (needs #salt and #iterations)
162+
def salted_password = @salted_password ||= compute_salted { super }
163+
164+
# Memoized ScramAlgorithm#client_key (needs #salt and #iterations)
165+
def client_key = @client_key ||= compute_salted { super }
166+
167+
# Memoized ScramAlgorithm#server_key (needs #salt and #iterations)
168+
def server_key = @server_key ||= compute_salted { super }
169+
158170
# Returns a new OpenSSL::Digest object, set to the appropriate hash
159171
# function for the chosen mechanism.
160172
#
@@ -194,6 +206,13 @@ def done?; @state == :done end
194206

195207
private
196208

209+
# Checks for +salt+ and +iterations+ before yielding
210+
def compute_salted
211+
salt in String or raise Error, "unknown salt"
212+
iterations in Integer or raise Error, "unknown iterations"
213+
yield
214+
end
215+
197216
# Need to store this for auth_message
198217
attr_reader :server_first_message
199218

0 commit comments

Comments
 (0)