Skip to content

Instantly share code, notes, and snippets.

@nanu-c

nanu-c/main.go Secret

Last active November 17, 2019 21:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nanu-c/f885b928b9e43a7167258dd70dc186d6 to your computer and use it in GitHub Desktop.
Save nanu-c/f885b928b9e43a7167258dd70dc186d6 to your computer and use it in GitHub Desktop.
Axolotl verivication key generation
package main
import (
"bytes"
"crypto/sha512"
"encoding/binary"
"encoding/hex"
"fmt"
"log"
"sort"
"github.com/nanu-c/textsecure/axolotl"
)
var iterations int
// https://github.com/signalapp/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/fingerprint/NumericFingerprintGenerator.java#L22
var FINGERPRINT_VERSION int16 = 0
var combinedFingerprints CombinedFingerprints
type CombinedFingerprints struct {
LocalFingerprint []byte
RemoteFingerprint []byte
}
func main() {
//https://github.com/signalapp/Signal-Android/blob/master/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java#L283
NumericFingerprintGenerator(5200)
localKey, _ := hex.DecodeString("")
remoteKey, _ := hex.DecodeString("")
localTel := []byte("")
remoteTel := []byte("")
localECKey := *axolotl.NewECPublicKey(localKey)
remoteECKey := *axolotl.NewECPublicKey(remoteKey)
var localECKeyA [1]axolotl.ECPublicKey
var b [1]axolotl.ECPublicKey
localECKeyA[0] = localECKey
remoteECKeyB[0] = remoteECKey
lFingerprint := getFingerprint(iterations, localTel, localECKeyA)
rFingerprint := getFingerprint(iterations, remoteTel, remoteECKeyB)
out := GetDisplayableFingerprint(lFingerprint, rFingerprint)
log.Println(out)
// a[1] = pub2
}
func NumericFingerprintGenerator(nIterations int) {
iterations = nIterations
}
func ScannableFingerprint(version int, localFingerprintData []byte, remoteFingerprintData []byte) {
// trim to 32
combinedFingerprints = *&CombinedFingerprints{
LocalFingerprint: localFingerprintData,
RemoteFingerprint: remoteFingerprintData,
}
}
//https://github.com/signalapp/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/fingerprint/NumericFingerprintGenerator.java#L104
func getFingerprint(iterations int, tel []byte, identityKeys [1]axolotl.ECPublicKey) []byte {
publicKey := getLogicalKeyBytes(identityKeys)
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, FINGERPRINT_VERSION)
if err != nil {
fmt.Println("binary.Write failed:", err)
}
digest := sha512.New()
fmt.Printf("% x", buf.Bytes())
version := buf.Bytes()
hash := append(version, publicKey...)
hash = append(hash, tel...)
for i := 0; i < iterations; i++ {
digest.Write(hash)
hash = digest.Sum(publicKey)
}
log.Println("hash", hash)
return hash
}
//https://github.com/signalapp/libsignal-protocol-java/blob/3662b6d705ae4162ad8b3a242daf35171edbb068/java/src/main/java/org/whispersystems/libsignal/fingerprint/NumericFingerprintGenerator.java#L122
func getLogicalKeyBytes(identityKeys [1]axolotl.ECPublicKey) []byte {
// sort IdentityKeyComparator
sorted := sortByteArrays(identityKeys)
sort.Sort(sorted)
log.Println(sorted)
var output []byte
for k := range sorted {
output = append(output, sorted[k].Serialize()...)
}
return output
}
// implement `Interface` in sort package.
type sortByteArrays [1]axolotl.ECPublicKey
func (b sortByteArrays) Len() int {
return len(b)
}
func (b sortByteArrays) Less(i, j int) bool {
// bytes package already implements Comparable for []byte.
switch bytes.Compare(b[i].Serialize(), b[j].Serialize()) {
case -1:
return true
case 0, 1:
return false
default:
log.Panic("not fail-able with `bytes.Comparable` bounded [-1, 1].")
return false
}
}
func (b sortByteArrays) Swap(i, j int) {
b[j], b[i] = b[i], b[j]
}
// Public
func SortByteArrays(src [1]axolotl.ECPublicKey) [1]axolotl.ECPublicKey {
sorted := sortByteArrays(src)
sort.Sort(sorted)
return sorted
}
func GetDisplayableFingerprint(localFingerprint []byte, remoteFingerprint []byte) string {
return getDisplayStringFor(localFingerprint) + getDisplayStringFor(remoteFingerprint)
}
//https://github.com/signalapp/libsignal-protocol-javascript/blob/f5a838f1ccc9bddb5e93b899a63de2dea9670e10/src/NumericFingerprint.js#L32
func getDisplayStringFor(fingerprint []byte) string {
return getEncodedChunk(fingerprint, 0) +
getEncodedChunk(fingerprint, 5) +
getEncodedChunk(fingerprint, 10) +
getEncodedChunk(fingerprint, 15) +
getEncodedChunk(fingerprint, 20) +
getEncodedChunk(fingerprint, 25)
}
//https://github.com/signalapp/libsignal-protocol-javascript/blob/f5a838f1ccc9bddb5e93b899a63de2dea9670e10/src/NumericFingerprint.js#L19
func getEncodedChunk(hash []byte, offset int) string {
chunk := byteArray5ToLong(hash, offset) % 100000
return fmt.Sprintf("%05d", chunk)
}
//https://github.com/signalapp/libsignal-protocol-java/blob/4f5e1ff299cea22cc75bb97249020a7da67b816d/java/src/main/java/org/whispersystems/libsignal/util/ByteUtil.java#L225
func byteArray5ToLong(bytes []byte, offset int) uint64 {
a := (uint64(bytes[offset]&0xff) << 32) |
(uint64(bytes[offset+1]&0xff) << 24) |
(uint64(bytes[offset+2]&0xff) << 16) |
(uint64(bytes[offset+3]&0xff) << 8) |
uint64(bytes[offset+4]&0xff)
log.Println(a)
return a
}
@Blackoverflow
Copy link

Line 58: err := binary.Write(buf, binary.LittleEndian, FINGERPRINT_VERSION)

As far as I understand, the fingerprint version is written in BigEndian by libsignal ByteUtil:

public static byte[] shortToByteArray(int value) {
    byte[] bytes = new byte[2];
    shortToByteArray(bytes, 0, value);
    return bytes;
  }

  public static int shortToByteArray(byte[] bytes, int offset, int value) {
    bytes[offset+1] = (byte)value;
    bytes[offset]   = (byte)(value >> 8);
    return 2;
  }

  public static int shortToLittleEndianByteArray(byte[] bytes, int offset, int value) {
    bytes[offset]   = (byte)value;
    bytes[offset+1] = (byte)(value >> 8);
    return 2;
  }

However, since the Fingerprint-Version is 0, this has no effect

@Blackoverflow
Copy link

func GetDisplayableFingerprint(localFingerprint []byte, remoteFingerprint []byte) string {
	return getDisplayStringFor(localFingerprint) + getDisplayStringFor(remoteFingerprint)
}

The DisplayableFingerprint is sorted by DisplayableFingerprint.getDisplayText():

public String getDisplayText() {
    if (localFingerprintNumbers.compareTo(remoteFingerprintNumbers) <= 0) {
      return localFingerprintNumbers + remoteFingerprintNumbers;
    } else {
      return remoteFingerprintNumbers + localFingerprintNumbers;
    }
  }

@Blackoverflow
Copy link

I think the hashing loop in getFingerprint() needs to be reworked:

https://golang.org/pkg/hash/#Hash

type Hash interface {
   // Write (via the embedded io.Writer interface) adds more data to the running hash.
   // It never returns an error.
   io.Writer

   // Sum appends the current hash to b and returns the resulting slice.
   // It does not change the underlying hash state.
   Sum(b []byte) []byte
 ...

https://docs.oracle.com/javase/7/docs/api/java/security/MessageDigest.html

public void update(byte[] input)

Updates the digest using the specified array of bytes.
public byte[] digest(byte[] input)

Performs a final update on the digest using the specified array of bytes, then completes the digest computation. That is, this method first calls update(input), passing the input array to the update method, then calls digest().

Parameters:
   input - the input to be updated before the digest is completed.
Returns:
   the array of bytes for the resulting hash value.
public byte[] digest()

Completes the hash computation by performing final operations such as padding. The digest is reset after this call is made.

Returns:
   the array of bytes for the resulting hash value.

As far as I understand, the MessageDigest of Java resets its internal state on digest() but Sum() in Golang does not. Furthermore, the parameter of digest() in Java has a complete different meaning than the parameter of Sum() in Golang.

Something like this should result in the same behaviour than the Java code:

for i := 0; i < iterations; i++ {
	var nilSlice []byte
	digest.Write(hash)
	digest.Write(publicKey)
	hash = digest.Sum(nilSlice)
	digest.Reset()
}

I have never written golang before and I haven't tried my code, but I hope the idea helps. :)

@nanu-c
Copy link
Author

nanu-c commented Nov 9, 2019

Hey Blackoverflow 1000 thanks for your suggestions, i will include all of them and make a new branch that we can test the different possibilities live in the app ;), again thanks for that

@Blackoverflow
Copy link

In order to group the numbers in blocks of five, I would add spaces within "getDisplayStringFor()".
Android seems to split them on interface level.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment