Cách tính băm md5 của tệp bằng javascript


104

Có cách nào để tính toán băm MD5 của tệp trước khi tải lên máy chủ bằng Javascript không?


1
Có liên quan mạnh: [Làm thế nào để tạo tổng kiểm tra và chuyển đổi thành 64 bit trong Javascript cho các tệp rất lớn mà không làm tràn RAM? ] ( stackoverflow.com/q/51987434/514235 )
iammilind

Câu trả lời:


92

Mặc dù có triển khai JS của thuật toán MD5, nhưng các trình duyệt cũ hơn thường không thể đọc tệp từ hệ thống tệp cục bộ .

Tôi đã viết điều đó vào năm 2009. Vậy còn các trình duyệt mới thì sao?

Với trình duyệt hỗ trợ FileAPI , bạn * có thể * đọc nội dung của tệp - người dùng phải chọn tệp đó, bằng một <input>phần tử hoặc kéo và thả. Kể từ tháng 1 năm 2013, đây là cách các trình duyệt chính xếp chồng lên nhau:


30
Ngoài việc không thể có quyền truy cập hệ thống tệp trong JS, tôi sẽ không đặt bất kỳ sự tin tưởng nào vào tổng kiểm tra do máy khách tạo. Vì vậy, việc tạo tổng kiểm tra trên máy chủ là bắt buộc trong mọi trường hợp.
Tomalak

4
@Tomalak Bạn cũng bắt buộc phải làm điều đó trên ứng dụng khách nếu bạn chỉ muốn tải lên nếu nó khác với những gì bạn đã có.
John

2
@John Chà, tuyên bố của tôi không loại trừ điều này. Kiểm tra phía máy khách nghiêm ngặt là để tạo sự thuận tiện cho người dùng (và do đó ít nhiều tùy chọn, tùy thuộc vào mức độ thuận tiện mà bạn muốn thực hiện). Mặt khác, kiểm tra phía máy chủ là bắt buộc.
Tomalak

hàm md5 trong pajhome.org.uk/crypt/md5 không hỗ trợ nhị phân làm đầu vào? Tôi nghĩ cần phải tính toán luồng nhị phân cho một hình ảnh được tải lên trong trình duyệt. Cảm ơn bạn.
jiajianrong

Nếu bạn có thể, hãy thêm một số mã mẫu vào câu trả lời của bạn. Nó sẽ giúp đỡ rất nhiều.
cbdeveloper

30

Tôi đã tạo một thư viện triển khai md5 gia tăng để băm các tệp lớn một cách hiệu quả. Về cơ bản, bạn đọc một tệp theo từng phần (để giữ cho bộ nhớ thấp) và băm nó tăng dần. Bạn có cách sử dụng cơ bản và các ví dụ trong readme.

Hãy lưu ý rằng bạn cần HTML5 FileAPI, vì vậy hãy nhớ kiểm tra nó. Có một ví dụ đầy đủ trong thư mục thử nghiệm.

https://github.com/satazor/SparkMD5


@Biswa đây là cách thực hiện của tôi. gist.github.com/marlocorridor/3e6484ae5a646bd7c625
Marlo

1
Này, công việc tuyệt vời! Tôi đã thử CryptoJS và không bao giờ có thể lấy được MD5 chính xác vì một số lý do, điều này hoạt động như một sự quyến rũ! Bất kỳ kế hoạch cho sha256? @satazor
xuất hiện

@cameck, thư viện tốt. Tuy nhiên, tôi đã thử nó hôm nay và có vẻ như có vấn đề với .end()phương pháp này. Nếu bạn gọi lại phương thức này thì lần sau nó sẽ cho kết quả sai. Vì .end()gọi .reset()nội bộ. Đây là một thảm họa mã hóa và không tốt cho việc viết thư viện.
iammilind

Cảm ơn thư viện! Đặt cùng một mã tối thiểu: dev.to/micmo/compute-md5-checksum-for-a-file-in-typescript-59a4
Qortex

27

khá dễ dàng để tính toán băm MD5 bằng cách sử dụng hàm MD5 của CryptoJSAPI HTML5 FileReader . Đoạn mã sau cho biết cách bạn có thể đọc dữ liệu nhị phân và tính toán băm MD5 từ một hình ảnh đã được kéo vào Trình duyệt của bạn:

var holder = document.getElementById('holder');

holder.ondragover = function() {
  return false;
};

holder.ondragend = function() {
  return false;
};

holder.ondrop = function(event) {
  event.preventDefault();

  var file = event.dataTransfer.files[0];
  var reader = new FileReader();

  reader.onload = function(event) {
    var binary = event.target.result;
    var md5 = CryptoJS.MD5(binary).toString();
    console.log(md5);
  };

  reader.readAsBinaryString(file);
};

Tôi khuyên bạn nên thêm một số CSS để xem khu vực Kéo & Thả:

#holder {
  border: 10px dashed #ccc;
  width: 300px;
  height: 300px;
}

#holder.hover {
  border: 10px dashed #333;
}

Bạn có thể tìm thêm thông tin về chức năng Kéo & Thả tại đây: File API & FileReader

Tôi đã thử nghiệm mẫu trong Google Chrome Phiên bản 32.


2
Vấn đề là nó readAsBinaryString()chưa được chuẩn hóa và không được hỗ trợ bởi Internet Explorer. Tôi đã không thử nghiệm nó trong Edge, nhưng ngay cả IE11 cũng không hỗ trợ nó.
StanE

@ user25163 Internet Explorer (và Opera Mini) dường như là trình duyệt hiện đại duy nhất không hỗ trợ readAsBinaryString(): caniuse.com/#feat=filereader - Microsoft Edge hỗ trợ nó.
Benny Neugebauer

Cảm ơn vì thông tin liên quan đến MS Edge! Tôi làm việc cho một công ty. Và bạn biết đấy, khách hàng thường sử dụng phần mềm cũ và thật khó để thuyết phục họ cập nhật phần mềm của họ. Tôi chỉ muốn chỉ ra rằng người ta phải cẩn thận readAsBinaryString()khi sử dụng vì nó không được các trình duyệt cũ hỗ trợ. Một giải pháp thay thế tôi đã tìm thấy là SparkMD5. Nó cũng sử dụng API FileReader nhưng là phương pháp readAsArrayBufferđược IE hỗ trợ. Và nó có thể xử lý các tập tin lớn bằng cách đọc chúng thành từng phần.
StanE

2
CryptoJS hiện hỗ trợ chuyển đổi từ ArrayBuffer sang Binary / WordArray thông qua:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad,

@WarrenParad Và đoạn mã trên sau đó sẽ được sửa đổi như thế nào để hoạt động với ArrayBuffer? Ahh, tìm thấy nó ở đây: stackoverflow.com/questions/28437181/…
TheStoryCoder

9

HTML5 + spark-md5Q

Giả sử bạn đang sử dụng một trình duyệt hiện đại (hỗ trợ API tệp HTML5), đây là cách bạn tính toán băm MD5 của một tệp lớn (nó sẽ tính toán băm trên các phần biến)

function calculateMD5Hash(file, bufferSize) {
  var def = Q.defer();

  var fileReader = new FileReader();
  var fileSlicer = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
  var hashAlgorithm = new SparkMD5();
  var totalParts = Math.ceil(file.size / bufferSize);
  var currentPart = 0;
  var startTime = new Date().getTime();

  fileReader.onload = function(e) {
    currentPart += 1;

    def.notify({
      currentPart: currentPart,
      totalParts: totalParts
    });

    var buffer = e.target.result;
    hashAlgorithm.appendBinary(buffer);

    if (currentPart < totalParts) {
      processNextPart();
      return;
    }

    def.resolve({
      hashResult: hashAlgorithm.end(),
      duration: new Date().getTime() - startTime
    });
  };

  fileReader.onerror = function(e) {
    def.reject(e);
  };

  function processNextPart() {
    var start = currentPart * bufferSize;
    var end = Math.min(start + bufferSize, file.size);
    fileReader.readAsBinaryString(fileSlicer.call(file, start, end));
  }

  processNextPart();
  return def.promise;
}

function calculate() {

  var input = document.getElementById('file');
  if (!input.files.length) {
    return;
  }

  var file = input.files[0];
  var bufferSize = Math.pow(1024, 2) * 10; // 10MB

  calculateMD5Hash(file, bufferSize).then(
    function(result) {
      // Success
      console.log(result);
    },
    function(err) {
      // There was an error,
    },
    function(progress) {
      // We get notified of the progress as it is executed
      console.log(progress.currentPart, 'of', progress.totalParts, 'Total bytes:', progress.currentPart * bufferSize, 'of', progress.totalParts * bufferSize);
    });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/q.js/1.4.1/q.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/2.0.2/spark-md5.min.js"></script>

<div>
  <input type="file" id="file"/>
  <input type="button" onclick="calculate();" value="Calculate" class="btn primary" />
</div>


8

Bạn cần sử dụng FileAPI. Nó có sẵn trong FF & Chrome mới nhất, nhưng không có sẵn trong IE9. Lấy bất kỳ triển khai JS md5 nào được đề xuất ở trên. Tôi đã thử điều này và bỏ nó vì JS quá chậm (vài phút trên các tệp hình ảnh lớn). Có thể truy cập lại nếu ai đó viết lại MD5 bằng cách sử dụng mảng đã nhập.

Mã sẽ trông giống như sau:

HTML:     
<input type="file" id="file-dialog" multiple="true" accept="image/*">

JS (w JQuery)

$("#file-dialog").change(function() {
  handleFiles(this.files);
});

function handleFiles(files) {
    for (var i=0; i<files.length; i++) {
        var reader = new FileReader();
        reader.onload = function() {
        var md5 = binl_md5(reader.result, reader.result.length);
            console.log("MD5 is " + md5);
        };
        reader.onerror = function() {
            console.error("Could not read the file");
        };
        reader.readAsBinaryString(files.item(i));
     }
 }

Webtoolkit MD5 được trỏ bởi bendewey thực hiện tốt hơn nhiều, 16s cho một tập tin đa-MB: webtoolkit.info/javascript-md5.html
Aleksandar Totic

1
Tôi đã quản lý để làm cho điều này hoạt động và cùng một băm md5 đang tạo (php: md5_file (...)) cho các tệp văn bản nhưng hình ảnh đang cho tôi kết quả khác? Đây có phải là điều gì đó liên quan đến dữ liệu nhị phân hay cách tải lên của nó?
Castles

Tôi khá chắc chắn rằng mã này không hoạt động với nhiều tệp, bởi vì onload là một lệnh gọi lại, readerbiến sẽ là tệp cuối cùng vào thời điểm các hàm onload được chạy.
Dave

CryptoJS hiện hỗ trợ chuyển đổi từ ArrayBuffer sang Binary / WordArray thông qua:CryptoJS.lib.WordArray.create(arrayBuffer);
Warren Parad,

4

Ngoài việc không thể có quyền truy cập hệ thống tệp trong JS, tôi sẽ không đặt bất kỳ sự tin tưởng nào vào tổng kiểm tra do máy khách tạo. Vì vậy, việc tạo tổng kiểm tra trên máy chủ là bắt buộc trong mọi trường hợp. - Tomalak 20 tháng 4 '09 lúc 14:05

Điều đó là vô ích trong hầu hết các trường hợp. Bạn muốn MD5 được tính toán ở phía máy khách, để bạn có thể so sánh nó với mã được biên dịch lại ở phía máy chủ và kết luận quá trình tải lên đã bị lỗi nếu chúng khác nhau. Tôi cần phải làm điều đó trong các ứng dụng làm việc với các tệp dữ liệu khoa học lớn, nơi việc nhận các tệp không bị gián đoạn là chìa khóa. Trường hợp của tôi rất đơn giản, vì người dùng đã tính toán MD5 từ các công cụ phân tích dữ liệu của họ, vì vậy tôi chỉ cần hỏi họ bằng một trường văn bản.




1

hy vọng bạn đã tìm thấy một giải pháp tốt bây giờ. Nếu không, giải pháp bên dưới là triển khai lời hứa ES6 dựa trên js-spark-md5

import SparkMD5 from 'spark-md5';

// Read in chunks of 2MB
const CHUCK_SIZE = 2097152;

/**
 * Incrementally calculate checksum of a given file based on MD5 algorithm
 */
export const checksum = (file) =>
  new Promise((resolve, reject) => {
    let currentChunk = 0;
    const chunks = Math.ceil(file.size / CHUCK_SIZE);
    const blobSlice =
      File.prototype.slice ||
      File.prototype.mozSlice ||
      File.prototype.webkitSlice;
    const spark = new SparkMD5.ArrayBuffer();
    const fileReader = new FileReader();

    const loadNext = () => {
      const start = currentChunk * CHUCK_SIZE;
      const end =
        start + CHUCK_SIZE >= file.size ? file.size : start + CHUCK_SIZE;

      // Selectively read the file and only store part of it in memory.
      // This allows client-side applications to process huge files without the need for huge memory
      fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
    };

    fileReader.onload = e => {
      spark.append(e.target.result);
      currentChunk++;

      if (currentChunk < chunks) loadNext();
      else resolve(spark.end());
    };

    fileReader.onerror = () => {
      return reject('Calculating file checksum failed');
    };

    loadNext();
  });

1

Đoạn mã sau đây cho thấy một ví dụ, có thể lưu trữ thông lượng 400 MB / s trong khi đọc và băm tệp.

Nó đang sử dụng một thư viện có tên là hash-wasm , dựa trên WebAssembly và tính toán băm nhanh hơn so với các thư viện chỉ js. Kể từ năm 2020, tất cả các trình duyệt hiện đại đều hỗ trợ WebAssembly.

const chunkSize = 64 * 1024 * 1024;
const fileReader = new FileReader();
let hasher = null;

function hashChunk(chunk) {
  return new Promise((resolve, reject) => {
    fileReader.onload = async(e) => {
      const view = new Uint8Array(e.target.result);
      hasher.update(view);
      resolve();
    };

    fileReader.readAsArrayBuffer(chunk);
  });
}

const readFile = async(file) => {
  if (hasher) {
    hasher.init();
  } else {
    hasher = await hashwasm.createMD5();
  }

  const chunkNumber = Math.floor(file.size / chunkSize);

  for (let i = 0; i <= chunkNumber; i++) {
    const chunk = file.slice(
      chunkSize * i,
      Math.min(chunkSize * (i + 1), file.size)
    );
    await hashChunk(chunk);
  }

  const hash = hasher.digest();
  return Promise.resolve(hash);
};

const fileSelector = document.getElementById("file-input");
const resultElement = document.getElementById("result");

fileSelector.addEventListener("change", async(event) => {
  const file = event.target.files[0];

  resultElement.innerHTML = "Loading...";
  const start = Date.now();
  const hash = await readFile(file);
  const end = Date.now();
  const duration = end - start;
  const fileSizeMB = file.size / 1024 / 1024;
  const throughput = fileSizeMB / (duration / 1000);
  resultElement.innerHTML = `
    Hash: ${hash}<br>
    Duration: ${duration} ms<br>
    Throughput: ${throughput.toFixed(2)} MB/s
  `;
});
<script src="https://cdn.jsdelivr.net/npm/hash-wasm"></script>
<!-- defines the global `hashwasm` variable -->

<input type="file" id="file-input">
<div id="result"></div>



-1

Tôi không tin rằng có cách trong javascript để truy cập nội dung của tệp tải lên. Vì vậy, bạn không thể xem nội dung tệp để tạo tổng MD5.

Tuy nhiên, bạn có thể gửi tệp đến máy chủ, sau đó có thể gửi lại tổng MD5 hoặc gửi lại nội dung tệp .. nhưng đó là rất nhiều công việc và có lẽ không đáng giá cho mục đích của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.