A while back I was looking for a way to generate short unique tokens to be used as authorization tokens in urls. I did some research and asked on ruby-talk. Here’s a summary of what I found.
Note that most of these algorithms can be translated to C#, Java, IronRuby/JRuby or your language and platform of choice.
My choice: Dave Bass’s rand().to_s() trick
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
if self.new_record? and self.access_token.nil?
self.access_token = rand(36**8).to_s(36)
end
end
endJamie Macey’s feedback
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:
>> 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 Davis: let’s put more words in it
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"John Mettraux’s Rufus Mnemo
rufus-mnemo has the ability to translate an integer into easy-to-remember words, based on Japanese syllables:
>> 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 use UUID – be careful with Solaris zones!
If you deploy to Solaris zones, be careful: some other libraries I had a look at, like assaf’s uuid, rely on macaddr, which doesn’t seem to work on Solaris zones.