Skip to content

Instantly share code, notes, and snippets.

@andrewrk
Created January 1, 2013 08:02
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewrk/4425843 to your computer and use it in GitHub Desktop.
Save andrewrk/4425843 to your computer and use it in GitHub Desktop.
how to generate minecraft hex digests
var crypto = require('crypto');
var assert = require('assert');
function mcHexDigest(str) {
var hash = new Buffer(crypto.createHash('sha1').update(str).digest(), 'binary');
// check for negative hashes
var negative = hash.readInt8(0) < 0;
if (negative) performTwosCompliment(hash);
var digest = hash.toString('hex');
// trim leading zeroes
digest = digest.replace(/^0+/g, '');
if (negative) digest = '-' + digest;
return digest;
}
function performTwosCompliment(buffer) {
var carry = true;
var i, newByte, value;
for (i = buffer.length - 1; i >= 0; --i) {
value = buffer.readUInt8(i);
newByte = ~value & 0xff;
if (carry) {
carry = newByte === 0xff;
buffer.writeUInt8(newByte + 1, i);
} else {
buffer.writeUInt8(newByte, i);
}
}
}
assert.strictEqual(mcHexDigest('Notch'), "4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48");
assert.strictEqual(mcHexDigest('jeb_'), "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1");
assert.strictEqual(mcHexDigest('simon'), "88e16a1019277b15d58faf0541e11910eb756f6");
@jonathanperret
Copy link

Please note that there are cases where this code tries to write 256 as a byte and writeUInt8 rejects it. Line 25 should read like this (or something equivalent):

      buffer.writeUInt8(carry ? 0 : newByte + 1, i);

@timvandam
Copy link

timvandam commented Apr 22, 2020

To make sure I understood minecraft's hexdigests correctly I implemented this myself. I'm dropping my code here in the hopes of it helping someone, I can remove it again if needed. I hope the comments are helpful. This example only generates the hex digest of the hash I coded in, if you need it for other things (like the code snippet above does) then you should simply change the hash

export function generateHexDigest (secret: Buffer, pubKey: Buffer) {
  // The hex digest is the hash made below.
  // However, when this hash is negative (meaning its MSB is 1, as it is in two's complement), instead of leaving it
  // like that, we make it positive and simply put a '-' in front of it. This is a simple process: as you always do
  // with 2's complement you simply flip all bits and add 1

  let hash = crypto.createHash('sha1')
    .update('') // serverId = just an empty string
    .update(secret)
    .update(pubKey)
    .digest()

  // Negative check: check if the most significant bit of the hash is a 1.
  const isNegative = (hash.readUInt8(0) & (1 << 7)) !== 0 // when 0, it is positive

  if (isNegative) {
    // Flip all bits and add one. Start at the right to make sure the carry works
    const inverted = Buffer.allocUnsafe(hash.length)
    let carry = 0
    for (let i = hash.length - 1; i >= 0; i--) {
      let num = (hash.readUInt8(i) ^ 0b11111111) // a byte XOR a byte of 1's = the inverse of the byte
      if (i === hash.length - 1) num++
      num += carry
      carry = Math.max(0, num - 0b11111111)
      num = Math.min(0b11111111, num)
      inverted.writeUInt8(num, i)
    }
    hash = inverted
  }
  let result = hash.toString('hex').replace(/^0+/, '')
  // If the result was negative, add a '-' sign
  if (isNegative) result = `-${result}`

  return result
}

@PoolloverNathan
Copy link

PoolloverNathan commented Apr 27, 2020

Replying to timvandam
To make sure I understood minecraft's hexdigests correctly I implemented this myself. I'm dropping my code here in the hopes of it helping someone, I can remove it again if needed. I hope the comments are helpful. This example only generates the hex digest of the hash I coded in, if you need it for other things (like the code snippet above does) then you should simply change the hash
export function generateHexDigest (secret: Buffer, pubKey: Buffer) {
  // The hex digest is the hash made below.
  // However, when this hash is negative (meaning its MSB is 1, as it is in two's complement), instead of leaving it
  // like that, we make it positive and simply put a '-' in front of it. This is a simple process: as you always do
  // with 2's complement you simply flip all bits and add 1

let hash = crypto.createHash('sha1')
.update('') // serverId = just an empty string
.update(secret)
.update(pubKey)
.digest()

// Negative check: check if the most significant bit of the hash is a 1.
const isNegative = (hash.readUInt8(0) & (1 << 7)) !== 0 // when 0, it is positive

if (isNegative) {
// Flip all bits and add one. Start at the right to make sure the carry works
const inverted = Buffer.allocUnsafe(hash.length)
let carry = 0
for (let i = hash.length - 1; i >= 0; i--) {
let num = (hash.readUInt8(i) ^ 0b11111111) // a byte XOR a byte of 1's = the inverse of the byte
if (i === hash.length - 1) num++
num += carry
carry = Math.max(0, num - 0b11111111)
num = Math.min(0b11111111, num)
inverted.writeUInt8(num, i)
}
hash = inverted
}
let result = hash.toString('hex').replace(/^0+/, '')
// If the result was negative, add a '-' sign
if (isNegative) result = -${result}

return result
}

I smell ECMAscript... delicious!

@janispritzkau
Copy link

janispritzkau commented Nov 3, 2022

This is much simpler using modern javascript:

function hexDigest(text) {
  const hash = crypto.createHash("sha1")
    .update(text)
    .digest()

  return BigInt.asIntN( // performs two's compliment
    160,           // hash size in bits
    hash.reduce(   // convert buffer to bigint using reduce
      (a, x) =>
        a << 8n |  // bit-shift the accumulator 8 bits (one byte) to the left
        BigInt(x), // fill lower byte just freed up by bit-shifting using bitwise or-operator
      0n           // start with accumulator value of 0
    )
  ).toString(16)   // display the result with base 16 (hex)
}

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