That is is basically a "fork" of blog article i'm constantly returning to. It seems that the blog is down:
Dave Bass proposed this which I picked up for my implementation (here for an 8-chars token):
>> rand(36**8).to_s(36)
=> "uur0cj2h"
The result can be used as an url; pretty neat. It relies on the ability of Fixnum to translate itself to a string in a given base (here we use base 36, which I rarely use!). This can be used in an ActiveRecord model for instance:
class Customer < ActiveRecord::Base
validates_presence_of :access_token
validates_uniqueness_of :access_token
protected
def before_validation_on_create
self.access_token = rand(36**8).to_s(36) if self.new_record? and self.access_token.nil?
end
end
Jamie proposed several options. First, use a substring of SHA1, which is “small enough to be usable, but still pseudo-random enough for temporary tokens to not be guessable” :
>> require 'digest'
=> []
>> Digest::SHA1.hexdigest("some-random-string")[8..16]
=> "2ebe5597f"
Another technique is to rely on ActiveSupport SecureRandom, and tweak the results a bit to get a url-friendly token. Here’s my final bit of code with this method:
>> require 'active_support'
=> []
>> ActiveSupport::SecureRandom.base64(8).gsub("/","_").gsub(/=+$/,"")
=> "AEWQyovNFo0"
Jamie’s last proposal is “not terribly robust, but functional” :
>> chars = ['A'..'Z', 'a'..'z', '0'..'9'].map{|r|r.to_a}.flatten
>> Array.new(6).map{chars[rand(chars.size)]}.join
=> "g64wdR"
Ryan proposed something totally different:
>> words = File.read("/usr/share/dict/words").split; max = words.size
=> 234936
>> "#{words[rand(max)]}-#{words[rand(max)]}"
=> "loquat-motorial"
The idea is interesting. You’ll need to ensure your dictionary doesn’t contain insults, if your user base cares about that :)
Another option Ryan got from Eric is to use the quite unknown bubble-babble to make hash values more readable:
>> require 'digest/bubblebabble'
=> true
Digest.bubblebabble(Digest::SHA1::hexdigest("random string")[8..12])
=> "xesik-fymak-gunax"
rufus-mnemo has the ability to translate an integer into easy-to-remember words, based on Japanese syllabes:
>> require 'rufus/mnemo'
>> s = Rufus::Mnemo::from_integer rand(8**5)
=> "bisoshi"
Pretty neat! The generated words are “easy to the latin ears”. Take care of the meaning if your users are Japanese-speaking.
If you deploy to Solaris zones, be careful about that: some other libraries I had a look at, like the very nice assaf’s uuid, are relying on macaddr, which doesn’t seem to work on Solaris Zone.
Please share them in comments.
This is helpful! Also discovered you can use
SecureRandom.urlsafe_base64
to get a url-safe random hash; you can specify the byte-length of the random number to get a shorter string (egSecureRandom.urlsafe_base64(5)
).