Search code examples
javasecurityauthenticationcryptographyrmi

Review of an authentication approach over RMI


I am doing a project wherein, to access the service, the client needs to authenticate itself(client has a username and password) over RMI. I want to achieve this without using SSL(hence, modification and sniffing are the major threats).

Here's a "token" that a client sends every time a service is accessed.

class Token {
    String username;
    byte[] encryptedText;
}

Encrypted Text variable contains a combination of password and timestamp that is encrypted with AES-256 CBC using the password itself as the key. eg:

encryptedText[0] = AES(20) //encrypt length of password 
encryptedText[1] = AES(30) //encrypt length of TimeStamp
encryptedText[2-21] = AES(password) //encrypted password
encryptedText[22-51] = AES(timestamp) //encrypted timestamp

The server on receiving this token retrieves the password for the username and tries to decrypt the encrypted text with the password as the key. If the result is not a combination of password and a recent TimeStamp, it is a wrong attempt.

I know this is not scalable, since every time a service is requested, a record has to be fetched, but is this secure?

From what I can think of:-

  • Prevents sniffing (encrypted)
  • Prevents modification attack (wouldn't result in correct authentication)
  • Prevents replay attacks (non-recent timestamp is an authentication failure)

Solution

  • I'm assuming that AES() doesn't just mean the pseudo-random permutation of a block cipher, but rather the block cipher with a mode of operation and a padding.

    It would be possible to recover the password through a padding oracle attack if it's possible to distinguish a bad padding from a good padding. This is very likely, because the response is probably finished earlier when there is a BadPaddingException and further computation cannot be done.

    The main two problems are that you're

    • not authenticating the ciphertext itself and
    • the password that was used to encrypt the plaintext is contained in the plaintext (could be recovered).

    This pseudo encryption code is better:

    // salt and masterHash can be cached for multiple requests
    salt = SecureRandom().nextBytes(8 bytes)
    
    // adjust iterations (65336) according to appropriate performance:
    masterHash = pbkdf2(password, salt, 65336)
    
    iv = SecureRandom().nextBytes(16 bytes)
    sessionKey = SecureRandom().nextBytes(16 bytes)
    encKey = hmacSha256(masterHash, "enc") // crop according to intended AES key size
    macKey = hmacSha256(masterHash, "mac")
    ciphertext = AES(encKey, iv, sessionKey + ts.length + ts)
    tag = hmacSha256(macKey, username + salt + iv + ciphertext)
    return salt + iv + ciphertext + tag
    

    Here + means concatenation and AES means AES in CBC mode with PKCS#7 padding.

    The receiver must first derive the macKey to then calculate the tag and then check whether the transmitted tag matches with the calculated tag. If it does then decrypt the ciphertext. If this works, then the sessionKey can be used for further communication.

    The only thing that an attacker could do, is preventing you from authenticating to the server by manipulating the transmitted request in order to produce a failed authentication on the server.