开发者

Completely random identifier of a given length

I would like to generate a completely random "unique" (I will ensure that using my model) identifier of a given (the length may varies) length containing numbers, letter and special characters

For example:

161551960578281|2.AQAIPhEcKsDLOVJZ.3600.1310065200.0-514191032|

Can someone please suggest the most efficient way to do that in Ruby on Rails?

EDIT: IMPORTANT: If it is possible please comment on how efficient your proposed solution is because this will be used every 开发者_如何转开发time a user enters a website!

Thanks


Using this for an access token is a different story than UUIDs. You need not only pseudo-randomness but additionally this needs to be a cryptographically secure PRNG. If you don't really care what characters you use (they don't add anything to the security) you could use something as the following, producing a URL-safe Base64-encoded access token. URL-safeness becomes important in case you append the token to URLs, similar to what some Java web apps do: "http://www.bla.com/jsessionid=". If you would use raw Base64 strings for that purpose you would produce potentially invalid URLs.

require 'securerandom'

def produce_token(length=32)
  token = SecureRandom.urlsafe_base64(length)
end

The probability of getting a duplicate is equal to 2^(-length). Since the output will be Base64-encoded, the actual output will be 4/3 * length long. If installed, this is based on the native OpenSSL PRNG implementation, so it should be pretty efficient in terms of performance. Should the OpenSSL extension not be installed, /dev/urandom will be used if available and finally, if you are on a Windows machine, CryptGenRandom would be used as fallback. Each of these options should be sufficiently performant. E.g., on my laptop running produce_tokena million times finishes in ~6s.


The best solution is:

require 'active_support/secure_random'
ActiveSupport::SecureRandom.hex(16) # => "00c62d9820d16b52740ca6e15d142854"

This will generate a cryptographically secure random string (i.e. completely unpredictable)

Similarly, you could use a library to generate UUIDs as suggested by others. In that case, be sure to use the random version (version 4) and make sure the implementation uses a cryptosecure random generator.

As anything related to security, rolling your own is not the best idea (even though I succumbed to it too, see first versions! :-). If you really want an homemade random string, here's a rewrite of tybro0103's approach:

require 'digest/sha1'
ALPHABET = "|,.!-0123456789".split(//) + ('a'..'z').to_a + ('A'..'Z').to_a

def random_string
    not_quite_secure = Array.new(32){ ALPHABET.sample }.join
    secure = Digest::SHA1.hexdigest(not_quite_secure)
end

random_string # => "2555265b2ff3ecb0a13d65a3d177b326733bc143"

Note that it hashes the random string, otherwise it could be subject to attack. Performance should be similar.


Universally Unique Identifieres - UUIDs are tricky to generate yourself ;-) If you want something really reliable, use the uuid4r gem and call it with UUID4R::uuid(1). This will spit out a uuid based on time and a hardware id (the computers mac address). So it's even unique across multiple machines if generated at the exact same time.

A requirement for uuid4r is the ossp-uuid c library which you can install with the packetmanager of your choice (apt-get install libossp-uuid libossp-uuid-dev on debian or brew install ossp-uuid on a mac with homebrew for example) or by manually downloading and compiling it of course.

The advantage of using uuid4r over a manual (simpler?) implementation is that it is a) truly unique and not just "some sort of pseudo random number generator kind of sometimes reliable" and b) it's fast (even with higher uuid versions) by using a native extension to the c library

require 'rubygems'
require 'uuid4r'
UUID4R::uuid(1) #=> "67074ea4-a8c3-11e0-8a8c-2b12e1ad57c3"
UUID4R::uuid(1) #=> "68ad5668-a8c3-11e0-b5b7-370d85fa740d"

update: regarding speed, see my (totally not scientific!) little benchmark over 50k iterations

      user     system      total        real
version 1  0.600000   1.370000   1.970000 (  1.980516)
version 4  0.500000   1.360000   1.860000 (  1.855086)

so on my machine, generating a uuid takes ~0.4 milliseconds (keep in mind I used 50000 iterations for the whole benchmark). hope that's fast enough for you

(following the "benchmark")

require 'rubygems'
require 'uuid4r'
require 'benchmark'

n = 50000
Benchmark.bm do |bm|
  bm.report("version 1") { n.times { UUID4R::uuid(1) } }
  bm.report("version 4") { n.times { UUID4R::uuid(4) } }
end

Update on heroku: the gem is available on heroku as well


def random_string(length=32)
    chars = (0..9).to_a.concat(('a'..'z').to_a).concat(('A'..'Z').to_a).concat(['|',',','.','!','-'])
    str = ""; length.times {str += chars.sample.to_s}
    str
end

The Result:

>> random_string(42)
=> "a!,FEv,g3HptLCImw0oHnHNNj1drzMFM,1tptMS|rO"


It is a bit trickier to generate random letters in Ruby 1.9 vs 1.8 due to the change in behavior of characters. The easiest way to do this in 1.9 is to generate an array of the characters you want to use, then randomly grab characters out of that array. See http://snippets.dzone.com/posts/show/491


You can check implementations here I used this one


I used current time in miliseconds to generate random but uniqure itentifier.

Time.now.to_f # => 1656041985.488494

Time.now.to_f.to_s.gsub('.', '') # => "16560419854884948"

this will give 17 digits number sometime it can give 16 digits number because if last digit after point (.) is 0 than it is ignore by to_f. so, I used rleft(17, '0')

example:

Time.now.to_f.to_s.gsub('.', '').ljust(17, '0') # => "1656041985488490"

Than I used to_s(36) to convert it into short length alphanumeric string.

Time.now.to_f.to_s.gsub('.', '').ljust(17, '0').to_i.to_s(36) # => "4j26hz9640k"

to_s(36) is radix base (36)

https://apidock.com/ruby/v2_5_5/Integer/to_s

if you want to limit the length than you can select first few digits of time in miliseconds:

Time.now.to_f.to_s.gsub('.', '').ljust(17, '0').first(12).to_i.to_s(36) # => "242sii2l"

but if you want the uniqueness accuracy in miliseconds than I would suggest to have atleast first(15) digits of time

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜