Skip to content

Instantly share code, notes, and snippets.

@mbajur
Created April 29, 2016 07:16
Show Gist options
  • Save mbajur/2aba832a6df3fc31fe7a82d3109cb626 to your computer and use it in GitHub Desktop.
Save mbajur/2aba832a6df3fc31fe7a82d3109cb626 to your computer and use it in GitHub Desktop.
How to create small, unique tokens in Ruby

How to create small, unique tokens in Ruby

That is is basically a "fork" of blog article i'm constantly returning to. It seems that the blog is down:

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
    self.access_token = rand(36**8).to_s(36) if self.new_record? and self.access_token.nil?
  end 
end

Jamie Macey 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. 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 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 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 use UUID – be careful with Solaris zones!

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.

Have you got more ?

Please share them in comments.

@danieldraper
Copy link

Another possible option is hashids

@whatcould
Copy link

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 (eg SecureRandom.urlsafe_base64(5)).

@psylone
Copy link

psylone commented Dec 10, 2019

Here's another one (requires Ruby 2.5.0+):

require 'securerandom'

SecureRandom.alphanumeric(6) # => B0YzrF

@ryzalyusoff
Copy link

Try this: Digest::SHA1.hexdigest([Time.now, rand].join)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment