├── .document ├── .github ├── dependabot.yml └── workflows │ ├── push_gem.yml │ └── test.yml ├── .gitignore ├── BSDL ├── COPYING ├── Gemfile ├── README.md ├── Rakefile ├── docs └── random.rb ├── lib └── securerandom.rb ├── rakelib └── epoch.rake ├── securerandom.gemspec └── test └── test_securerandom.rb /.document: -------------------------------------------------------------------------------- 1 | LICENSE.txt 2 | README.md 3 | docs/ 4 | lib/ 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/securerandom' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/securerandom 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | steps: 25 | - name: Harden Runner 26 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 27 | with: 28 | egress-policy: audit 29 | 30 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 34 | with: 35 | bundler-cache: true 36 | ruby-version: ruby 37 | 38 | - name: Publish to RubyGems 39 | uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 40 | 41 | - name: Create GitHub release 42 | run: | 43 | tag_name="$(git describe --tags --abbrev=0)" 44 | gh release create "${tag_name}" --verify-tag --generate-notes 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} 47 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 8 | with: 9 | engine: cruby 10 | min_version: 3.1 11 | 12 | test: 13 | needs: ruby-versions 14 | name: build (${{ matrix.ruby }} / ${{ matrix.os }}) 15 | strategy: 16 | matrix: 17 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 18 | os: [ ubuntu-latest, macos-latest, windows-latest ] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - name: git config 22 | run: | 23 | git config --global core.autocrlf false 24 | git config --global core.eol lf 25 | git config --global advice.detachedHead 0 26 | - uses: actions/checkout@v4 27 | - name: Set up Ruby 28 | uses: ruby/setup-ruby@v1 29 | with: 30 | ruby-version: ${{ matrix.ruby }} 31 | - name: Install dependencies 32 | run: bundle install 33 | - name: Run test 34 | run: rake test 35 | - name: RDoc coverage 36 | run: rdoc -C 37 | if: ${{ matrix.ruby == needs.ruby-versions.outputs.latest && matrix.os == 'ubuntu-latest' }} 38 | - id: build 39 | run: | 40 | rake build:checksum 41 | ls -l pkg/*.gem checksums/* 42 | cat checksums/* 43 | echo "pkg=${GITHUB_REPOSITORY#*/}-${PLATFORM:-${RUNNING_OS%-*}}" >> $GITHUB_OUTPUT 44 | env: 45 | RUNNING_OS: ${{matrix.os}} 46 | if: >- 47 | ${{ 48 | github.event_name == 'push' && 49 | matrix.ruby == needs.ruby-versions.outputs.latest 50 | }} 51 | shell: bash 52 | - name: Upload package 53 | uses: actions/upload-artifact@v4 54 | with: 55 | path: pkg/*.gem 56 | name: ${{steps.build.outputs.pkg}} 57 | if: ${{ steps.build.outcome == 'success' }} 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /checksums/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rake" 4 | gem "test-unit" 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Securerandom 2 | 3 | This library is an interface to secure random number generators which are 4 | suitable for generating session keys in HTTP cookies, etc. 5 | 6 | ## Installation 7 | 8 | Add this line to your application's Gemfile: 9 | 10 | ```ruby 11 | gem 'securerandom' 12 | ``` 13 | 14 | And then execute: 15 | 16 | $ bundle install 17 | 18 | Or install it yourself as: 19 | 20 | $ gem install securerandom 21 | 22 | ## Usage 23 | 24 | Generate random hexadecimal strings: 25 | 26 | ```ruby 27 | require 'securerandom' 28 | 29 | SecureRandom.hex(10) #=> "52750b30ffbc7de3b362" 30 | SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559" 31 | SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23" 32 | ``` 33 | 34 | Generate random base64 strings: 35 | 36 | ```ruby 37 | SecureRandom.base64(10) #=> "EcmTPZwWRAozdA==" 38 | SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg==" 39 | SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8" 40 | ``` 41 | 42 | Generate random binary strings: 43 | 44 | ```ruby 45 | SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301" 46 | SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" 47 | ``` 48 | 49 | Generate alphanumeric strings: 50 | 51 | ```ruby 52 | SecureRandom.alphanumeric(10) #=> "S8baxMJnPl" 53 | SecureRandom.alphanumeric(10) #=> "aOxAg8BAJe" 54 | ``` 55 | 56 | Generate UUIDs: 57 | 58 | ```ruby 59 | SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" 60 | SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" 61 | ``` 62 | 63 | ## Development 64 | 65 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 66 | 67 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 68 | 69 | ## Contributing 70 | 71 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/securerandom. 72 | 73 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList["test/**/test_*.rb"] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /docs/random.rb: -------------------------------------------------------------------------------- 1 | # This file is only for RDoc 2 | 3 | # Random provides an interface to Ruby's pseudo-random number generator, or 4 | # PRNG. 5 | # 6 | # See also Random::Formatter module that adds convenience methods to generate 7 | # various forms of random data. 8 | 9 | class Random 10 | end 11 | -------------------------------------------------------------------------------- /lib/securerandom.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: us-ascii -*- 2 | # frozen_string_literal: true 3 | 4 | require 'random/formatter' 5 | 6 | # == Secure random number generator interface. 7 | # 8 | # This library is an interface to secure random number generators which are 9 | # suitable for generating session keys in HTTP cookies, etc. 10 | # 11 | # You can use this library in your application by requiring it: 12 | # 13 | # require 'securerandom' 14 | # 15 | # It supports the following secure random number generators: 16 | # 17 | # * openssl 18 | # * /dev/urandom 19 | # * Win32 20 | # 21 | # SecureRandom is extended by the Random::Formatter module which 22 | # defines the following methods: 23 | # 24 | # * alphanumeric 25 | # * base64 26 | # * choose 27 | # * gen_random 28 | # * hex 29 | # * rand 30 | # * random_bytes 31 | # * random_number 32 | # * urlsafe_base64 33 | # * uuid 34 | # 35 | # These methods are usable as class methods of SecureRandom such as 36 | # +SecureRandom.hex+. 37 | # 38 | # If a secure random number generator is not available, 39 | # +NotImplementedError+ is raised. 40 | 41 | module SecureRandom 42 | 43 | # The version 44 | VERSION = "0.4.1" 45 | 46 | class << self 47 | # Returns a random binary string containing +size+ bytes. 48 | # 49 | # See Random.bytes 50 | def bytes(n) 51 | return gen_random(n) 52 | end 53 | 54 | # Compatibility methods for Ruby 3.2, we can remove this after dropping to support Ruby 3.2 55 | def alphanumeric(n = nil, chars: ALPHANUMERIC) 56 | n = 16 if n.nil? 57 | choose(chars, n) 58 | end if RUBY_VERSION < '3.3' 59 | 60 | private 61 | 62 | # :stopdoc: 63 | 64 | # Implementation using OpenSSL 65 | def gen_random_openssl(n) 66 | return OpenSSL::Random.random_bytes(n) 67 | end 68 | 69 | # Implementation using system random device 70 | def gen_random_urandom(n) 71 | ret = Random.urandom(n) 72 | unless ret 73 | raise NotImplementedError, "No random device" 74 | end 75 | unless ret.length == n 76 | raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes" 77 | end 78 | ret 79 | end 80 | 81 | begin 82 | # Check if Random.urandom is available 83 | Random.urandom(1) 84 | alias gen_random gen_random_urandom 85 | rescue RuntimeError 86 | begin 87 | require 'openssl' 88 | rescue NoMethodError 89 | raise NotImplementedError, "No random device" 90 | else 91 | alias gen_random gen_random_openssl 92 | end 93 | end 94 | 95 | # :startdoc: 96 | 97 | # Generate random data bytes for Random::Formatter 98 | public :gen_random 99 | end 100 | end 101 | 102 | SecureRandom.extend(Random::Formatter) 103 | -------------------------------------------------------------------------------- /rakelib/epoch.rake: -------------------------------------------------------------------------------- 1 | task "build" => "date_epoch" 2 | 3 | task "date_epoch" do 4 | ENV["SOURCE_DATE_EPOCH"] = IO.popen(%W[git -C #{__dir__} log -1 --format=%ct], &:read).chomp 5 | end 6 | -------------------------------------------------------------------------------- /securerandom.gemspec: -------------------------------------------------------------------------------- 1 | name = File.basename(__FILE__, ".gemspec") 2 | version = ["lib", Array.new(name.count("-")+1).join("/")].find do |dir| 3 | break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| 4 | /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 5 | end rescue nil 6 | end 7 | 8 | Gem::Specification.new do |spec| 9 | spec.name = name 10 | spec.version = version 11 | spec.authors = ["Tanaka Akira"] 12 | spec.email = ["akr@fsij.org"] 13 | 14 | spec.summary = %q{Interface for secure random number generator.} 15 | spec.description = %q{Interface for secure random number generator.} 16 | spec.homepage = "https://github.com/ruby/securerandom" 17 | spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0") 18 | spec.licenses = ["Ruby", "BSD-2-Clause"] 19 | 20 | spec.metadata["changelog_uri"] = spec.homepage + "/releases" 21 | spec.metadata["homepage_uri"] = spec.homepage 22 | spec.metadata["source_code_uri"] = spec.homepage 23 | 24 | # Specify which files should be added to the gem when it is released. 25 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 26 | spec.files = Dir.chdir(__dir__) do 27 | `git ls-files -z`.split("\x0").reject do |f| 28 | (File.expand_path(f) == __FILE__) || 29 | f.start_with?(*%w[bin/ test/ spec/ features/ docs/ rakelib/ .document .git Gemfile Rakefile]) 30 | end 31 | end 32 | spec.bindir = "exe" 33 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 34 | spec.require_paths = ["lib"] 35 | end 36 | -------------------------------------------------------------------------------- /test/test_securerandom.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'securerandom' 4 | 5 | # This testcase does NOT aim to test cryptographically strongness and randomness. 6 | class TestSecureRandom < Test::Unit::TestCase 7 | 8 | def setup 9 | @it = SecureRandom 10 | end 11 | 12 | def test_alphanumeric_with_chars 13 | assert_nothing_raised(ArgumentError) do 14 | @it.alphanumeric(1, chars: ("0".."9").to_a) 15 | end 16 | end 17 | 18 | # This test took 2 minutes on my machine. 19 | # And 65536 times loop could not be enough for forcing PID recycle. 20 | # TODO: We should run this test only on GitHub Actions. 21 | def test_s_random_bytes_is_fork_safe 22 | begin 23 | require 'openssl' 24 | rescue LoadError 25 | return 26 | end 27 | SecureRandom.random_bytes(8) 28 | pid, v1 = forking_random_bytes 29 | assert(check_forking_random_bytes(pid, v1), 'Process ID not recycled?') 30 | end if false # ENV["CI"] && RUBY_PLATFORM =~ /darwin/ && `sw_vers -productVersion`.to_i > 13 # for Apple Silicon 31 | 32 | def forking_random_bytes 33 | r, w = IO.pipe 34 | pid = fork { 35 | r.close 36 | w.write SecureRandom.random_bytes(8) 37 | w.close 38 | } 39 | w.close 40 | v = r.read(8) 41 | r.close 42 | Process.waitpid2(pid) 43 | [pid, v] 44 | end 45 | 46 | def check_forking_random_bytes(target_pid, target) 47 | (65536 * 1.5).to_i.times do 48 | pid = fork { 49 | if $$ == target_pid 50 | v2 = SecureRandom.random_bytes(8) 51 | if v2 == target 52 | exit(1) 53 | else 54 | exit(2) 55 | end 56 | end 57 | exit(3) 58 | } 59 | pid, status = Process.waitpid2(pid) 60 | case status.exitstatus 61 | when 1 62 | raise 'returned same sequence for same PID' 63 | when 2 64 | return true 65 | end 66 | end 67 | false # not recycled? 68 | end 69 | 70 | def test_with_openssl 71 | begin 72 | require 'openssl' 73 | rescue LoadError 74 | return 75 | end 76 | assert_equal(Encoding::ASCII_8BIT, @it.send(:gen_random_openssl, 16).encoding) 77 | 65.times do |idx| 78 | assert_equal(idx, @it.send(:gen_random_openssl, idx).size) 79 | end 80 | end 81 | 82 | def test_repeated_gen_random 83 | assert_nothing_raised NoMethodError, '[ruby-core:92633] [Bug #15847]' do 84 | @it.gen_random(1) 85 | @it.gen_random(1) 86 | end 87 | end 88 | end 89 | --------------------------------------------------------------------------------