Skip to content
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

Switching SecureTokens to Base58 #18347

Merged
merged 1 commit into from Jan 9, 2015

Conversation

robertomiranda
Copy link
Contributor

@robertomiranda
Copy link
Contributor Author

cc @dhh, @chancancode

@dhh
Copy link
Member

dhh commented Jan 5, 2015

Looks good to me, but I'll let someone in the know actually review the algorithm ;).

# The result may contain A-Z, a-z, 0-9
#
# p SecureRandom.base64 #=> "08101M3q0p1v0z2h21171Y413e2M3r0t"
# p SecureRandom.base64 #=> "3D3H0M3m1T3c1E2P290I0C0t3f432t1J"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be examples of base62

@robertomiranda robertomiranda force-pushed the has_secure_token_base62 branch 2 times, most recently from f55489c to 36338d8 Compare January 5, 2015 16:49
require 'active_support/core_ext/securerandom'
class SecureRandomTest < ActiveSupport::TestCase
def test_generate_base62_string
puts SecureRandom.base62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

puts is not needed

@matthewd
Copy link
Member

matthewd commented Jan 5, 2015

An actual base62 encoder seems needlessly complicated for our purposes... see my comments on previous discussion.

@robertomiranda
Copy link
Contributor Author

whats's of issue with use base64 instead of base62?

@matthewd
Copy link
Member

matthewd commented Jan 5, 2015

@robertomiranda it contains characters we don't want. That was also discussed to death in the previous PR.

@dhh
Copy link
Member

dhh commented Jan 5, 2015

@matthewd What are you suggesting as an alternative here?

@matthewd
Copy link
Member

matthewd commented Jan 5, 2015

Something like:

random_bytes(chars).unpack("C*").map do |byte|
  idx = byte % 64
  idx = random_number(62) if idx >= 62
  BASE62_CHARS[idx]
end.join

And having looked closer, I've just realised that @robertomiranda's implementation is actually Wrong: it's not generating a uniformly random string.

module SecureRandom
# SecureRandom.base62 generates a random base62 string.
#
# The argument _n_ specifies the length, in bytes, of the random number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The argument is named bytes, not n.

@matthewd
Copy link
Member

matthewd commented Jan 5, 2015

The above is obviously assuming that random_bytes is faster / otherwise betterer than a series of calls to random_number... otherwise, life gets much simpler:

''.tap do |string|
  length.times { string << BASE62_CHARS[random_number(62)] }
end

@egilburg
Copy link
Contributor

egilburg commented Jan 5, 2015

it's not generating a uniformly random string.

idx = random_number(62) if idx >= 62 is not uniform either (it'll produce a slight bias towards integers than towards letters). But is the goal here to produce a cryptographically secure random string, or something "close enough"?

Also consider readability for those who may want to write these strings down - some existing algorithms, for exampe, avoid uppercase I (i) as it looks like lowercase l (L), same with o/O/0.

@chancancode
Copy link
Member

Also consider readability for those who may want to write these strings down - some existing algorithms, for exampe, avoid uppercase I (i) as it looks like lowercase l (L), same with o/O/0.

Assuming a uniform distribution, with Base58 at 24 characters we are still getting 140 random bits, which is still miles better than a random UUID, so I guess why not. It's useful for things like "write down these TFA lock out code in case you lost your phone", or "enter this manually if your phone can't scan this QR code" scenario.

But is the goal here to produce a cryptographically secure random string, or something "close enough"?

IMO, "close enough" is a dangerous territory here. Once you are off the tracks, all bets are off and it's difficult to tell what you are up against (e.g. Is this still equivalent or better than a random UUID? How would we prove it?). It also makes it difficult to put any guarantee on it hence recommend using this API for any specific purpose.

@dhh
Copy link
Member

dhh commented Jan 5, 2015

👍 on what Godfrey said.

On Jan 5, 2015, at 11:46, Godfrey Chan notifications@github.com wrote:

Also consider readability for those who may want to write these strings down - some existing algorithms, for exampe, avoid uppercase I (i) as it looks like lowercase l (L), same with o/O/0.

Assuming a uniform distribution, with Base58 at 24 characters we are still getting 140 random bits, which is still miles better than a random UUID, so I guess why not. It's useful for things like "write down these TFA lock out code in case you lost your phone", or "enter this manually if your phone can't scan this QR code" scenario.

But is the goal here to produce a cryptographically secure random string, or something "close enough"?

IMO, "close enough" is a dangerous territory here. Once you are off the tracks, all bets are off and it's difficult to tell what you are up against (e.g. Is this still equivalent or better than a random UUID? How would we prove it?). It also makes it difficult to put any guarantee on it hence recommend using this API for any specific purpose.


Reply to this email directly or view it on GitHub.

@matthewd
Copy link
Member

matthewd commented Jan 6, 2015

@egilburg I'm obviously being thick, because that looks uniform to me. Care to expand?

@egilburg
Copy link
Contributor

egilburg commented Jan 6, 2015

@matthewd perhaps I'm the thicker one here, but this is a bit confusing to me:

  idx = byte % 64
  idx = random_number(62) if idx >= 62

It seems you get one of 64 variations, but 2 of those are "bad" so you "reroll" them to a backup implementation. Why would just idx = random_number(62) not work then?

@matthewd
Copy link
Member

matthewd commented Jan 6, 2015

256 values in a byte; % 64 is safe (drops two bits), % 62 would bias.

I did offer a vastly simpler alternative that just uses random_number from the outset... this version was building on an (untested) assumption that doing most of the work with a single call to random_bytes might be noticeably more efficient.

@robertomiranda robertomiranda force-pushed the has_secure_token_base62 branch 4 times, most recently from dea0c7a to 92c18db Compare January 9, 2015 23:14
@robertomiranda robertomiranda changed the title Switching SecureTokens to Base62 Switching SecureTokens to Base58 Jan 9, 2015
Update Secure Token Doc [ci skip]

remove require securerandom, core_ext/securerandom already do that ref 7e00605
guilleiguaran added a commit that referenced this pull request Jan 9, 2015
@guilleiguaran guilleiguaran merged commit d8b8a72 into rails:master Jan 9, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants