Pages

Thursday, June 28, 2012

Secure Remote Password Protocol in Scala

I was very interested in authentication and bumped against SRP. SRP is a protocol that helps in achieving authentication and is defined at http://srp.stanford.edu/. Since, I did not see any implementation of this in my current fav programming language- Scala, I decided to write a small scala based SRP v6a implementation. The code is hosted on Github at https://github.com/shreyaspurohit/SRPScala and is MIT licensed opensource. Feel free to contribute. I have provided an example implementation using Play 2.0 web framework. This is hosted at http://srp.bitourea.com/. The client is authenticated by server, then client authenticates that its speaking to the right server and finally continues to get the secret page over AES 128bit PBE encrypted channel. Since, just scala server side code is not sufficient- there is also srp.js that tags along for the client side javascript. The code scala and JS is small and can be easily understood. Just implement SRPServer trait and you are good to go. The example play 2.0 app provides a sample implementation- ExampleSRPServer.scala. This uses file for storage, you can use DB or any other destination for storage. The usage of javascript is provided as example in login.scala.html. It shows a very cleanly the data that is exchanged between client and server. This is how the example app works. These are just important code snippets. Look in Github for the complete code.

var aVal = a();
var Aval = A(aVal);
var AvalHex = Aval.toHexString();
var username = $('#txtUsername').val();
var password = $('#txtPassword').val();

Client gets a, Aval, username, password for the current session.
$.ajax({
url: '/login',
type: 'POST',
data: {"username" : username, "A" : AvalHex}
})

Client posts username and A to the server.
val Aval = params.get("A")
val username = params.get("username")

Server gets the A and username from request.
val result = ExampleSRPServer.getSessionWithClientParameters(username(0), Aval(0))
if(result.isDefined){
val (sessionId, hSessionId, s, bvalStr) = result.get
ExampleSRPServer.saveSession(username(0), sessionId, hSessionId)
println(">>> session id: " + sessionId)
Ok(s + "," + bvalStr)//Return the salt s and calculated value B
}else{
Ok("Error: Authentication Failure")
}

Server calculates the expected session id. There are some more authentication is real code. If the results are defined then save the current session, return s and bvalStr. Refer to SRP design to understand these parameters.
var exch1 = data.split(",")
var Sval = currentSession(exch1[0], exch1[1], aVal, Aval, password)
sessionId=Sval;
setCookie("username", username);
validateServer();

From the data received from server, client gets the current session. At this point, server has authenticated the client. Now, we need to make sure we are speaking to the right server.
function validateServer(){
var username = getCookie("username");
Khex = K(sessionId)
var Mval = M(Khex)
var verifier = serverVerifier(Mval, Khex)

$.ajax({
url: '/validateServer',
type: 'GET',
data: {"username" : username, "K" : Mval}
})

Client set the Khex, and the Mval for the current session. It posts username and Mval to server. The response from server must match the serverVerifier generated.
def validateServer(username:String, M:String) = Action(implicit request =>{
import srp._
val (sessionId, hSessionId) = ExampleSRPServer.getSessionWithHash(username)
if(BigInt(ExampleSRPServer.M(hSessionId), 16).equals(BigInt(M,16))){
Ok(ExampleSRPServer.verifier(hSessionId,M))
}else{
Ok("Error: Authentication Failure. M mismatch.")
}
})

The server returns the verifier. The server and client have authenticated to each other. K has never been sent over the wire, so we use K as secret key for this session to encrypt data exchange between server and client. This is shown in method- getTheSecretPage() in login.scala.html. On the server side check Auth.scala for AES magic. The enc method on the server side does the job in scala.
def enc(username:String, sessionId:String, page:String) = 
EncryptedAuthenticatedPage(username, sessionId, page){decrypted =>
decrypted._3 match {
case "secret" => Ok(decrypted._5(views.html.secret().body))
case _ => NotFound(<h1>Page not found</h1>)
}
}