rubyguides

String#crypt deprecated

str.crypt(salt_str)

Overview

String#crypt encrypts the receiver string using the POSIX crypt(3) function with the provided salt. The salt is a string of at least 2 bytes whose format determines which hashing algorithm gets used.

This method has been part of Ruby’s standard library since very early versions, but it is deprecated in Ruby 2.5 and later (Feature #14915). Ruby now emits a deprecation warning and delegates to the string-crypt gem when it is available. Removal from core is planned.

For new code, install the gem explicitly:

gem install string-crypt

Installing the gem downloads and compiles the native C extension that wraps the system’s crypt(3) call so the updated implementation is on disk. The require statement then pulls the gem into the current Ruby process so that String#crypt uses the updated implementation instead of the built-in stub. Without the require, Ruby still emits the deprecation warning and falls through to the default version.

require 'string/crypt'

Salt Format

The salt string controls algorithm selection through its prefix. Understanding the prefix is critical — using the wrong one silently produces weak or broken output.

Salt PrefixAlgorithmNotes
(no prefix, 2 chars)Traditional DES2^56 keyspace — trivially brute-forced
$1$MD5Author abandoned it — do not use
$3$MD4/ATHCompletely broken — avoid
$5$SHA-256Acceptable for non-password use
$6$SHA-512Better option for hash-based applications
$2a$Blowfish (BSD)Used by BSD crypt_b

Modular formats also support a rounds= parameter to increase computational cost:

"$6$rounds=5000$mysecretsalt$"

The rounds= parameter is optional and controls how many iterations of the hash function run before producing the final output. Higher values make brute-force attacks substantially slower — 5000 is a common starting point for SHA-512 — but increase the time your application spends on every hash computation. Because the default value varies by operating system, specifying rounds= explicitly makes the behaviour portable across platforms.

Basic Usage

Without the gem, or with traditional DES (the default when using a 2-character salt):

"hello".crypt("ab")
# => "RXkJ8M2N8vGjQ"  # output varies by OS

SHA-512 with a high round count is substantially harder to brute-force than the default DES algorithm, which has a keyspace of only 2^56. The modular format embeds the algorithm identifier, the iteration count, and the salt directly in the output string, so you can verify a hash later without storing those parameters separately. On systems that bundle libxcrypt, this syntax works natively; on others, installing the string-crypt gem provides the same support.

require 'string/crypt'
"hello".crypt("$6$rounds=5000$mysecretsalt$")
# => "$6$rounds=5000$mysecretsalt$o.jP9zGQdJ..."

SHA-256 produces a shorter hash string than SHA-512 while still offering strong collision resistance for non-password use cases. It runs faster on 32-bit systems and is sometimes preferred when output length matters for storage or when embedding the hash in a URL. Both algorithms support the same rounds= parameter, so the choice between them comes down to the output size versus verification speed trade-off.

"hello".crypt("$5$rounds=1000$saltvalue$")
# => "$5$rounds=1000$saltvalue$eG2IUf9O1..."

Deprecation Warning

Ruby 2.5 deprecates String#crypt (Feature #14915) because the POSIX crypt(3) function varies too much between platforms to be a reliable standard-library method. The warning appears every time the method is invoked — it nudges codebases toward the string-crypt gem, which provides a consistent implementation across operating systems. When the gem is installed, Ruby silently delegates to it and the warning disappears:

$ ruby -e '"foo".crypt("ab")'
-e:1: warning: String#crypt is deprecated; use string-crypt gem
=> "ab9QbfRTt7p2A"

The warning appears on stderr. It does not stop execution. Ruby delegates to the string-crypt gem when it is available, so installing the gem silences the warning while keeping the same interface.

Salt Requirements

The salt argument must be at least 2 bytes:

"hello".crypt("a")
# => ArgumentError: salt too short (need >=2 bytes)

A salt that is too short raises an ArgumentError immediately, which is the expected behaviour for invalid input. A malformed salt, however, does not raise an error; it silently produces an incorrect or weak hash. This is one of the method’s sharpest edges: a typo in the prefix — such as round instead of rounds, or a missing $ delimiter — goes unnoticed at runtime. The output still looks like a hash, so the mistake may not surface until much later when verification fails:

# Typo in rounds parameter: "round" instead of "rounds"
"foo".crypt("$5$round=1000$salt$")
# => silently wrong hash, no error raised

Gotchas and platform differences

OS dependency. crypt(3) is a system call, so the supported algorithms and output format vary by operating system. A hash produced on Linux cannot be verified on macOS or Windows. This is especially important for cross-platform applications.

macOS limitations. On macOS, crypt(3) does not support modular crypt modes ($5$, $6$, $2a$). It always falls back to the traditional DES algorithm, regardless of what salt prefix you provide. The method will not raise an error — it just ignores the prefix and produces a DES hash.

# On macOS, a valid SHA-512 salt prefix is silently ignored.
# The result is a traditional DES hash, not SHA-512.
"password".crypt("$6$rounds=5000$salt$")
# Linux (glibc): "$6$rounds=5000$salt$F3mPm..."
# macOS:        "abXXXXXXXX"  (DES, ignores the prefix)

Thread safety. On some operating systems, crypt(3) is not thread-safe. Ruby’s implementation adds a mutex to protect the call, but this introduces overhead in multi-threaded code and does not eliminate platform-level race conditions.

Weak default. When no prefix is used (or a 2-character salt is given), the method uses traditional DES. This has a keyspace of only 2^56 — easily brute-forced on modern hardware. Never use the default for anything security-related.

MD5 is abandoned. The $1$ prefix uses MD5, whose author officially abandoned it for password hashing. It is retained for legacy compatibility only.

When not to use this method

  • Password storage in new applications — use bcrypt, argon2, or scrypt instead
  • Any security-sensitive hashing — DES and MD5 are broken
  • Cross-platform applications — algorithm support varies
  • Anything requiring portability between operating systems
# Use bcrypt for password hashing instead of crypt:
require 'bcrypt'
hash = BCrypt::Password.create("my password")
# => "$2a$12$..."  (cost factor and salt embedded in output)

See Also