1
\$\begingroup\$

C++ code version (description copied from it)

gameKeys id is authentication function of an MMO game server. When someone wants to play with friends, he sends request to server, server calls generateId, then gameKeys id and returns it as JSON using encodeJSON to player via API. When a friend establishes connection, he sends the game id and his key, server calls gameKeys id, where id is user data, and then checks user's key.

Algorithm is:

  • use current time and internal counter to generate id if it's not provided, or use provided id
  • make fast crypto hash using Blake2 function of id and secret(Yes, I know about HMAC and using as key, but in this case it doesn't matter)
  • split 64-bit hash to four 16-bit values, one hash for one user
  • make JSON object from id and keys and return it

I want to make stable code: exploit-free, no memory leaks and high speed (for high load and DDOS prevention). I'm experimenting now, so the algorithm was first written in JavaScript, then in C++ and now in Haskell.

I think Haskell is best in writing stable code.

Please review the Haskell code; I think it can be improved (I'm new to Haskell). Code compiles and works same as JS and C++. Also please advise me, which programming language is better for this: JS, C++ , Haskell or Go?

Here's the Haskell code; install dependencies cabal install blake2 hex split unix-time json to run it:

{-# LANGUAGE DeriveDataTypeable #-}
import Data.IORef
import System.IO.Unsafe
import qualified Crypto.Hash.BLAKE2.BLAKE2b as Blake2
import qualified Data.ByteString.Char8 as Char8
import Data.Hex
import Data.Char
import Data.List.Split
import Data.UnixTime
import Text.JSON.Generic

topsecret = "TOPSECRET"

dateCounter :: IORef Int
dateCounter = unsafePerformIO (newIORef 0)

hashBytes :: Char8.ByteString -> Char8.ByteString
hashBytes = Blake2.hash 64 mempty;
hashHex :: String -> String
hashHex =  (map toLower) . Char8.unpack . hex . hashBytes . Char8.pack;
makeKeys :: String -> [String]
makeKeys = (chunksOf 32) . hashHex;

data GameAuth = GameAuth
  { id :: Int
  , keys :: [String]
  } deriving (Show, Data, Typeable)

gameKeys id = GameAuth id (makeKeys ((show id)++"|keys|"++topsecret))

generateId = do
  time <- getUnixTime
  counter <- readIORef dateCounter
  modifyIORef dateCounter addCounter
  return $ shiftTime time counter
shiftTime time counter  = (fromEnum (utSeconds time))*1000 + counter
addCounter c = (c+1) `mod` 1000



main = do
  id <- generateId
  putStrLn (encodeJSON (gameKeys id))

Here's the node.js version; it needs blakejs (run npm i blakejs to install it):

const TOPSECRET = "TOPSECRET", blake = require("blakejs").blake2bHex;
let dateCounter = 0;

function gameKey(checkId){
  let id = checkId || (Date.now()/1000>>0)*1000+(dateCounter++%1000),
    keys = blake(`${id}|keys|${TOPSECRET}`).match(/.{32}/g);
  return {id, keys};
}
console.log(gameKey());
\$\endgroup\$
3
\$\begingroup\$

As modifyIORef notes, use modifyIORef' for counters.

I would wrap the unsafe usage of unsafePerformIO into a safer interface.

withCounter :: (Int -> IO a) -> IO a
withCounter action = let counter = unsafePerformIO $ newIORef 0 in do
  i <- readIORef counter
  modifyIORef' counter (+1)
  action i

generateId = withCounter $ \date -> do
  time <- fromEnum . utSeconds <$> getUnixTime
  return $ time * 1000 + mod date 1000

The || logic doesn't seem to be in the Haskell code yet.

I wouldn't give a name to a private function used only once.

Many of your brackets are redundant. The hint tool can tell you this.

It seems you want to use some but not all of what you define in other code. The code that uses this could make a difference in the review.

Data.Unique may interest you for id generation if you do not actually care about your exact behavior.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.