top of page
  • pranaypourkar

Pseudorandomness: Understanding PRNGs in Cryptography



Cryptography refers to the techniques and algorithms that are used for secure communication and data in storage. It allows to communicate securely even if a middle man trying to interfere in between. It transforms message or data into a form that is unreadable to unauthorized parties. Cryptography is used to achieve various goals such as ...

  • Confidentiality -> Keeping information secret through encryption

  • Integrity -> ensuring data hasn't been tampered with

  • Authentication -> Verifying the identity of a sender or recipient

  • Non-Repudiation -> preventing denial of message origin or receipt

For more details, refer to https://en.wikipedia.org/wiki/Cryptography


What are cryptographic algorithms and cryptographic protocols ? A cryptographic algorithm is a mathematical procedure that is used to encrypt or decrypt data. For eg. Symmetric-key encryption algorithms, Asymmetric-key encryption algorithms, Hash functions. A cryptographic protocol is a set of rules that define how cryptographic algorithms are used to achieve a specific security goal. For eg. Transport Layer Security (TLS), Secure Shell (SSH), Internet Protocol Security (IPSec).

Random Numbers play an essential role during encryption. Generating truly random numbers is quite challenging. True random number generators rely on physical processes like atmospheric noise. Computer programs depend on PRNGs which stands for "Pseudorandom Number Generator". It is a software algorithm or a mathematical function that generates a sequence of numbers that appear to be random but are actually deterministic.


PRNGs are widely used in Cryptography for various purposes such as ...

  • Generating Encryption-Decryption Keys -> Cryptographic algorithms often require random keys of specific lengths and PRNGs can be used to generate such keys.

  • Creating Nonces -> Nonces are random numbers to ensure the freshness and uniqueness of data.

  • Initialization Vectors (IVs) -> Encryption modes such as Cipher Block Chaining (CBC) requires an initialization vector for the first block of plaintext to introduce randomness and ensure the uniqueness of ciphertexts.

  • Message Authentication Codes (MACs) -> A MAC is a fixed-size output generated by applying a cryptographic algorithm and a secret key to a message. MAC acts as a unique fingerprint or tag for the message, allowing the recipient to verify the integrity and authenticity of the message.

  • Shuffling Data -> In cryptographic protocols like SSH (Secure Shell), there is a need to shuffle data. This can be done using a PRNG to generate a random sequence of numbers. The data can then be shuffled according to this sequence. This helps to ensure that the data is not easily predictable.

  • Salting -> PRNGs can be used to generate random salts, which are random values added to data during password hashing to prevent precomputed attacks like rainbow table attacks.

  • Key Stretching -> Key stretching algorithms like PBKDF2 (Password-Based Key Derivation Function 2) utilizes PRNGs to generate additional pseudorandom bits to make the process time-consuming for attackers.



Let's understand what seed is in context of PRNG.

A seed is an initial value used to initialize a pseudorandom number generator. The seed is essentially the starting point for generating a sequence of random or pseudorandom numbers.

When a PRNG is initialized with a seed, it sets its internal state to a specific value determined by the seed. From this initial state, the PRNG algorithm generates subsequent numbers in the sequence. The sequence of numbers produced by the PRNG is deterministic, meaning that given the same seed, the PRNG will always produce the same sequence of numbers.


Seeds are an important part of PRNG because they allow for reproducibility. By using the same seed, you can generate the same sequence of random numbers. This property is useful in scenarios such as testing, debugging, or situations where you need to ensure consistent behavior across multiple runs of a program.


It is important to note that using the same seed also means that the generated sequence is predictable. If an attacker or an unintended party discovers or guesses the seed, they can reproduce the same sequence of random numbers and potentially put the security or integrity of the system at risk.


For secure random number generation, it's crucial to use high-quality, unpredictable, and securely generated seeds. Cryptographically secure pseudorandom number generators (CSPRNGs) often incorporate entropy from various sources (e.g., system events, hardware noise, user input) to generate suitable seed values.


CSPRNGs like java.security.SecureRandom in Java provides a secure source of randomness that can be used as a seed.
For security and isolation reasons, it is recommended to generate and use distinct seeds for different applications unless there are specific circumstances that requires use of the same seed.
Entropy measures the unpredictability or randomness of a given set of data. It quantifies the amount of randomness present in a system or source of data. It is essential for generating secure and unpredictable cryptographic keys, random numbers, and other cryptographic materials. A high level of entropy mean that the generated values are difficult to guess or reproduce, making them resistant to cryptographic attacks.


Now, let's discuss safety of PRNGs.

Not all pseudorandom number generators (PRNGs) are considered to be safe or suitable for cryptographic purposes. It is crucial to use cryptographically secure PRNGs (CSPRNGs) to ensure the generated random numbers possess the necessary properties for security. CSPRNGs are designed to provide randomness that is indistinguishable from true randomness and makes even difficult for a attacker with substantial computational resources.


In the case of secure and cryptographically secure pseudorandom number generators (PRNGs), the generated values are designed to be unpredictable, making it extremely difficult to predict the values without knowledge of the internal state or seed of the generator. The underlying algorithms and techniques used in these PRNGs are specifically designed to resist various types of attacks and provide strong randomness guarantees.


Some of the properties which a secure PRNG should follow are ...

  • Unpredictability -> The generated random numbers should be unpredictable and difficult to guess or reproduce. This property ensures that an attacker cannot determine the next value in the sequence based on previously generated numbers or any other information.

  • Cryptographic Security -> The PRNG should be resistant to cryptographic attacks, including attempts to deduce the internal state or seed, predict future values, or find patterns in the generated sequence.

  • Statistical Randomness -> The PRNG should produce random numbers that exhibit statistical randomness properties. This means that the generated sequence should resemble the properties of a truly random sequence such as uniform distribution, independence of generated values and lack of biases.

  • Periodicity -> The PRNG should have a long period, meaning that it takes a significant number of generated values before the sequence repeats. A longer period ensures that the same values are not repeated within a short time frame and reduces the chances of a predictable pattern emerging.

  • Efficiency -> The PRNG should be efficient in terms of computation and resource usage. It should generate random numbers with reasonable speed and without excessive memory consumption.

  • Seed Sensitivity -> The PRNG should be sensitive to changes in the seed value and a small change in the seed should result in a completely different sequence of generated numbers. This will ensure that even small modifications to the seed will result in entirely different and unrelated random sequences.

For example in Java, java.util.Random is an insecure PRNG whereas java.security.SecureRandom is considered to be Secure PRNG.


  • java.util.Random

// Insecure java.util.Random
// Create a new instance of the Random class
Random random = new Random();
log.info("Random nextBoolean- {}", random.nextBoolean());
log.info("Random nextInt- {}", random.nextInt());
log.info("Random nextLong- {}", random.nextLong());
log.info("Random nextFloat- {}", random.nextFloat());
log.info("Random nextDouble- {}", random.nextDouble());
log.info("Random nextGaussian- {}", random.nextGaussian());

byte[] randomBytes = new byte[5];
random.nextBytes(randomBytes);
log.info("Random nextBytes- {}", randomBytes);

// Generate and log a random byte value
byte randomByte = (byte) random.nextInt(Byte.MAX_VALUE + 1);
log.info("Random nextByte- {}", randomByte);

// Generate and log a random short value
short randomShort = (short) random.nextInt(Short.MAX_VALUE + 1);
log.info("Random nextShort- {}", randomShort);

// Generate and log a random character value
char randomChar = (char) (random.nextInt(Character.MAX_VALUE + 1));
log.info("Random nextChar- {}", randomChar);

  • java.security.SecureRandom

        // Secure java.security.SecureRandom
        SecureRandom secureRandom = new SecureRandom();
        log.info("Name of Algo implemented in SecureRandom- {}", secureRandom.getAlgorithm());
        log.info("Provider of SecureRandom- {}", secureRandom.getProvider());

        byte[] secureRandomBytes = new byte[5];
        secureRandom.nextBytes(secureRandomBytes);
        log.info("SecureRandom nextBytes- {}", secureRandomBytes);

        // Generate and log a random BigInteger using SecureRandom
        log.info("SecureRandom random BigInteger- {}", new BigInteger(128, secureRandom));

        // Generate and log a random boolean value using SecureRandom
        log.info("SecureRandom nextBoolean- {}", secureRandom.nextBoolean());

        // Generate and log a random long value using SecureRandom
        log.info("SecureRandom nextLong- {}", secureRandom.nextLong());

        // Generate and log a random float value between 0.0 (inclusive) and 1.0 (exclusive) using SecureRandom
        log.info("SecureRandom nextFloat- {}", secureRandom.nextFloat());

        // Generate and log a random double value between 0.0 (inclusive) and 1.0 (exclusive) using SecureRandom
        log.info("SecureRandom nextDouble- {}", secureRandom.nextDouble());

        // Generate and log a random Gaussian-distributed double value with mean 0.0 and standard deviation 1.0 using SecureRandom
        log.info("SecureRandom nextGaussian- {}", secureRandom.nextGaussian());
        
        // Generate a user-specified number of random seed bytes. The seed bytes are suitable for initializing
        // other random number generators or for other purposes requiring high-quality random data
        byte[] seed = secureRandom.generateSeed(16); // Generate 16 bytes of seed

        // Set the seed of the SecureRandom instance using the provided seed bytes
        // The seed alters the initial state of the random number generator, influencing the subsequent random 
        // number generation
        byte[] seed2 = secureRandom.generateSeed(6); // Get seed bytes from a secure source
        secureRandom.setSeed(seed); // Set the seed

        // Get a SecureRandom object that implements the specified algorithm. 
        // The algorithm name can be one of the available secure random number generator algorithms supported 
        // by the Java platform
        try {
            SecureRandom secureRandom2 = SecureRandom.getInstance("SHA1PRNG");
            log.info("SecureRandom using Algorithm- {}", secureRandom2.getAlgorithm());
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // Get the list of supported algorithms
        Provider[] providers = Security.getProviders();
        for (Provider provider : providers) {
            Set<Provider.Service> services = provider.getServices();
            for (Provider.Service service : services) {
                if (service.getType().equals("SecureRandom")) {
                    log.info(service.getAlgorithm());
                }
            }
        }

While the implementation of certain methods like nextLong(), nextFloat(), nextDouble(), nextGaussian() may be the same between SecureRandom and Random, the underlying random number generator used by SecureRandom is different. The random numbers generated by SecureRandom are considered more suitable for cryptographic purposes due to the strength of the underlying random number generator while Random uses a simpler algorithm that may not meet the same security requirements.

Let's go through some of the uses cases of PRNG for Key Generation and Token Generation

Key Generation:

There are several types of keys used in Cryptography for different purposes and algorithms. Some of the keys are explained below.

  • Symmetric Key Generation -> PRNGs are commonly used to generate random or pseudorandom values for symmetric key generation. The generated key serves as a shared secret between the communicating parties for symmetric encryption and decryption. The security of the encryption scheme relies on the quality and randomness of the generated key.

  • Asymmetric Key Generation -> PRNGs are also used in generating random or pseudorandom values for asymmetric key pairs, such as RSA or Elliptic Curve Cryptography (ECC) keys. These keys are important for asymmetric encryption, digital signatures, and key exchange protocols. The secure and unpredictable nature of the PRNG helps ensure the strength and security of the generated key pairs.

  • Hashing Keys -> Hash functions are cryptographic algorithms that take an input and produce a fixed-size output called a hash value or digest. Hashing keys are not used for encryption or decryption but for validating the integrity of data such as passwords. Hash functions such as SHA-256 (Secure Hash Algorithm 256-bit) or MD5 (Message Digest Algorithm 5) generate hash values from the input data.

  • Key Derivation Keys -> Key derivation functions (KDFs) generate derived keys from existing keys or from other input. These derived keys are used for specific purposes such as key expansion, password-based key derivation, or key strengthening. Examples of KDFs include PBKDF2 (Password-Based Key Derivation Function 2) and HKDF (HMAC-based Extract-and-Expand Key Derivation Function).


Sample code for Asymmetric Key Generation

   try {
            // Generate an RSA key pair
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
            keyGen.initialize(2048); // Set the key size
            KeyPair keyPair = keyGen.generateKeyPair();

            // Get the public and private keys
            // Note: In practice, handle and store the keys securely
            byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
            byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();

            // Print the generated keys
            log.info("Public Key with size {}: {}", publicKeyBytes.length, publicKeyBytes);
            log.info("Private Key with size {}: {}", privateKeyBytes.length, privateKeyBytes);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
In RSA algorithm, the key pair consists of a public key and a private key. The key size determines the length of the modulus, which is a large number used in the RSA calculations. The keyGen.initialize(2048) line sets the key size to 2048 bits. The generated RSA key pair will have a 2048-bit modulus. The actual byte sizes of the public and private keys will vary based on the specific encoding format used (such as DER or PKCS#8) and may include additional components such as metadata.

Token Generation:

  • Authentication Tokens -> PRNGs are usually used in generating secure authentication tokens or session identifiers. These tokens are used to authenticate users or establish and maintain secure sessions. PRNGs ensure the uniqueness and unpredictability of the tokens, making it challenging for attackers to guess or impersonate valid tokens.

  • Access Tokens -> PRNGs can be used to generate access tokens for controlling access to resources or APIs. These tokens provide authorization and grant specific privileges or permissions to the requesting entities. PRNGs help create secure and random access tokens that are difficult to forge or tamper with.

Sample code for Token Generation

// Generate secure random token bytes
// Set token length
byte[] tokenBytes = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(tokenBytes);

// Encode to Base64 format
String token = Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes);
log.info("Token: {}", token);



Thank you for taking the time to read this post. I hope that you found it informative and useful in your own development work.

16 views0 comments

Recent Posts

See All
bottom of page