简体   繁体   中英

Calculate Dropbox's Content Hash locally in Dart/Flutter

I'm trying to calculate the content_hash of a Dropbox file, following their algorithm, Dropbox docs here , however, I cannot get the overall hash (result of Step 4) correct.

I can produce the correct hashes on each block, so I suspect the issue starts with the conversion to the concantenated binary string (Step 3).

Per Dropbox's docs, there are 4 steps involved:

  1. Split the file into blocks of 4 MB (4,194,304 or 4 * 1024 * 1024 bytes). The last block (if any) may be smaller than 4 MB.
  2. Compute the hash of each block using SHA-256.
  3. Concatenate the hash of all blocks in the binary format to form a single binary string.
  4. Compute the hash of the concatenated string using SHA-256. Output the resulting hash in hexadecimal format.

They provide an example, with the resulting hash values, which I can only replicate up to step 2 (unfortunately, they do not provide an example of the single binary string for Step 3)

My code is as follows:

Future<void> getDropContentHash() async {

  String getHash(List<int> bytes) => sha256.convert(bytes).toString();

  String toBinary(String string) {
    /// converting string to binary string, following this SO Answer
    /// https://stackoverflow.com/a/25906926/12132021
    return string.codeUnits.map((x) => x.toRadixString(2).padLeft(8, '0')).join();
  }

  /// Nasa Milky Way Example:
  final response = await http.get(Uri.parse('https://www.dropbox.com/static/images/developers/milky-way-nasa.jpg'));
  final bytes = response.bodyBytes;

  /// Step 1: 
  /// Split the file into blocks of 4 MB (4,194,304 or 4 * 1024 * 1024 bytes). The last block (if any) may be smaller than 4 MB.

  final chunks = bytes.slices(4 * 1024 * 1024);
  print(chunks.length); // 3, as expected in the Nasa example

  /// Step 2: 
  /// Compute the hash of each block using SHA-256.
  final chunkHashes = <String>[];
  for(final chunk in chunks) {
    final blockHash = getHash(chunk);
    chunkHashes.add(blockHash);
  }
  print(chunkHashes);

  /*
  Per the docs, the chunkHashes are correct:
  [
    2a846fa617c3361fc117e1c5c1e1838c336b6a5cef982c1a2d9bdf68f2f1992a,
    c68469027410ea393eba6551b9fa1e26db775f00eae70a0c3c129a0011a39cf9,
    7376192de020925ce6c5ef5a8a0405e931b0a9a8c75517aacd9ca24a8a56818b
  ]
  */

  /// Step 3: 
  /// Concatenate the hash of all blocks in the binary format to form a single binary string.
  /// I suspect the issue with the `toBinary` method, converting to a single binary string:
  final concatenatedString = chunkHashes.map((chunkHash) => toBinary(chunkHash)).join();
  print(concatenatedString);

  /// Step 4: 
  /// Compute the hash of the concatenated string using SHA-256. Output the resulting hash in hexadecimal format.
  final contentHash = getHash(concatenatedString.codeUnits);
  print(contentHash);

  /*
  Does NOT yield the correct value of:
  485291fa0ee50c016982abbfa943957bcd231aae0492ccbaa22c58e3997b35e0

  Instead, it yields:
  0f63574f6c7cf29d1735e8f5ec4ef63abb3bc5c1b8618c6455a2c554bc844396
  */

}

Can anyone help me get the correct overall hash of 485291fa0ee50c016982abbfa943957bcd231aae0492ccbaa22c58e3997b35e0 ?

I figured it out. I did not need to convert to binary string. I just needed to continue working with bytes, and only convert to a string after the overall hash (step 4)

The resulting code (simplified & refactored) to calculate the Dropbox content hash locally:

  Future<void> hashNasaImage() async {
    final response = await http.get(Uri.parse('https://www.dropbox.com/static/images/developers/milky-way-nasa.jpg'));
    final bytes = response.bodyBytes;
    final contentHash = getDropContentHash(bytes);
    print(contentHash); 
    // 485291fa0ee50c016982abbfa943957bcd231aae0492ccbaa22c58e3997b35e0
  }

  String getDropContentHash(List<int> bytes) {
    List<int> getHash(List<int> bytes) => sha256.convert(bytes).bytes;    
    final chunks = bytes.slices(4 * 1024 * 1024);
    final chunkHashes = chunks.fold(<int>[], (buffer, chunk) => buffer..addAll(getHash(chunk)));
    return Digest(getHash(chunkHashes)).toString();
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM