// TOTPFromBytes converts a byte array to a totp object // it stores the state of the TOTP object, like the key, the current counter, the client offset, // the total amount of verification failures and the last time a verification happened func TOTPFromBytes(encryptedMessage []byte, issuer string) (*Totp, error) { // init the cryptoengine engine, err := cryptoengine.InitCryptoEngine(issuer) if err != nil { return nil, err } // decrypt the message data, err := engine.Decrypt(encryptedMessage) if err != nil { return nil, err } // new reader reader := bytes.NewReader([]byte(data.Text)) // otp object otp := new(Totp) // get the lenght lenght := make([]byte, 4) _, err = reader.Read(lenght) // read the 4 bytes for the total lenght if err != nil && err != io.EOF { return otp, err } totalSize := bigendian.FromInt([4]byte{lenght[0], lenght[1], lenght[2], lenght[3]}) buffer := make([]byte, totalSize-4) _, err = reader.Read(buffer) if err != nil && err != io.EOF { return otp, err } // skip the total bytes size startOffset := 0 // read key size endOffset := startOffset + 4 keyBytes := buffer[startOffset:endOffset] keySize := bigendian.FromInt([4]byte{keyBytes[0], keyBytes[1], keyBytes[2], keyBytes[3]}) // read the key startOffset = endOffset endOffset = startOffset + keySize otp.key = buffer[startOffset:endOffset] // read the counter startOffset = endOffset endOffset = startOffset + 8 b := buffer[startOffset:endOffset] otp.counter = [8]byte{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]} // read the digits startOffset = endOffset endOffset = startOffset + 4 b = buffer[startOffset:endOffset] otp.digits = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) // // read the issuer size startOffset = endOffset endOffset = startOffset + 4 b = buffer[startOffset:endOffset] issuerSize := bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) // read the issuer string startOffset = endOffset endOffset = startOffset + issuerSize otp.issuer = string(buffer[startOffset:endOffset]) // read the account size startOffset = endOffset endOffset = startOffset + 4 b = buffer[startOffset:endOffset] accountSize := bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) // read the account string startOffset = endOffset endOffset = startOffset + accountSize otp.account = string(buffer[startOffset:endOffset]) // read the steps startOffset = endOffset endOffset = startOffset + 4 b = buffer[startOffset:endOffset] otp.stepSize = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) // read the offset startOffset = endOffset endOffset = startOffset + 4 b = buffer[startOffset:endOffset] otp.clientOffset = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) // read the total failuers startOffset = endOffset endOffset = startOffset + 4 b = buffer[startOffset:endOffset] otp.totalVerificationFailures = bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) // read the offset startOffset = endOffset endOffset = startOffset + 8 b = buffer[startOffset:endOffset] ts := bigendian.FromUint64([8]byte{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]}) otp.lastVerificationTime = time.Unix(int64(ts), 0) // read the hash type startOffset = endOffset endOffset = startOffset + 4 b = buffer[startOffset:endOffset] hashType := bigendian.FromInt([4]byte{b[0], b[1], b[2], b[3]}) switch hashType { case 1: otp.hashFunction = crypto.SHA256 break case 2: otp.hashFunction = crypto.SHA512 break default: otp.hashFunction = crypto.SHA1 } return otp, err }
// ToBytes serialises a TOTP object in a byte array // Sizes: 4 4 N 8 4 4 N 4 N 4 4 4 8 4 // Format: |total_bytes|key_size|key|counter|digits|issuer_size|issuer|account_size|account|steps|offset|total_failures|verification_time|hashFunction_type| // hashFunction_type: 0 = SHA1; 1 = SHA256; 2 = SHA512 // The data is encrypted using the cryptoengine library (which is a wrapper around the golang NaCl library) // TODO: // 1- improve sizes. For instance the hashFunction_type could be a short. func (otp *Totp) ToBytes() ([]byte, error) { // check Totp initialization if err := totpHasBeenInitialized(otp); err != nil { return nil, err } var buffer bytes.Buffer // caluclate the length of the key and create its byte representation keySize := len(otp.key) keySizeBytes := bigendian.ToInt(keySize) //bigEndianInt(keySize) // caluclate the length of the issuer and create its byte representation issuerSize := len(otp.issuer) issuerSizeBytes := bigendian.ToInt(issuerSize) // caluclate the length of the account and create its byte representation accountSize := len(otp.account) accountSizeBytes := bigendian.ToInt(accountSize) totalSize := 4 + 4 + keySize + 8 + 4 + 4 + issuerSize + 4 + accountSize + 4 + 4 + 4 + 8 + 4 totalSizeBytes := bigendian.ToInt(totalSize) // at this point we are ready to write the data to the byte buffer // total size if _, err := buffer.Write(totalSizeBytes[:]); err != nil { return nil, err } // key if _, err := buffer.Write(keySizeBytes[:]); err != nil { return nil, err } if _, err := buffer.Write(otp.key); err != nil { return nil, err } // counter counterBytes := bigendian.ToUint64(otp.getIntCounter()) if _, err := buffer.Write(counterBytes[:]); err != nil { return nil, err } // digits digitBytes := bigendian.ToInt(otp.digits) if _, err := buffer.Write(digitBytes[:]); err != nil { return nil, err } // issuer if _, err := buffer.Write(issuerSizeBytes[:]); err != nil { return nil, err } if _, err := buffer.WriteString(otp.issuer); err != nil { return nil, err } // account if _, err := buffer.Write(accountSizeBytes[:]); err != nil { return nil, err } if _, err := buffer.WriteString(otp.account); err != nil { return nil, err } // steps stepsBytes := bigendian.ToInt(otp.stepSize) if _, err := buffer.Write(stepsBytes[:]); err != nil { return nil, err } // offset offsetBytes := bigendian.ToInt(otp.clientOffset) if _, err := buffer.Write(offsetBytes[:]); err != nil { return nil, err } // total_failures totalFailuresBytes := bigendian.ToInt(otp.totalVerificationFailures) if _, err := buffer.Write(totalFailuresBytes[:]); err != nil { return nil, err } // last verification time verificationTimeBytes := bigendian.ToUint64(uint64(otp.lastVerificationTime.Unix())) if _, err := buffer.Write(verificationTimeBytes[:]); err != nil { return nil, err } // has_function_type switch otp.hashFunction { case crypto.SHA256: sha256Bytes := bigendian.ToInt(1) if _, err := buffer.Write(sha256Bytes[:]); err != nil { return nil, err } break case crypto.SHA512: sha512Bytes := bigendian.ToInt(2) if _, err := buffer.Write(sha512Bytes[:]); err != nil { return nil, err } break default: sha1Bytes := bigendian.ToInt(0) if _, err := buffer.Write(sha1Bytes[:]); err != nil { return nil, err } } // encrypt the TOTP bytes engine, err := cryptoengine.InitCryptoEngine(otp.issuer) if err != nil { return nil, err } // init the message to be encrypted message, err := cryptoengine.NewMessage(buffer.String(), message_type) if err != nil { return nil, err } // encrypt it encryptedMessage, err := engine.NewEncryptedMessage(message) if err != nil { return nil, err } return encryptedMessage.ToBytes() }