-
-
Save nanu-c/f885b928b9e43a7167258dd70dc186d6 to your computer and use it in GitHub Desktop.
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 | |
} |
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;
}
}
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. :)
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
In order to group the numbers in blocks of five, I would add spaces within "getDisplayStringFor()".
Android seems to split them on interface level.
As far as I understand, the fingerprint version is written in BigEndian by libsignal ByteUtil:
However, since the Fingerprint-Version is 0, this has no effect