In my client, a user selects a photo for upload, we do a small transformation on it using the canvas to resize it to a max height/width (preserving aspect ratio) of 2500 pixels. Once the user submits, the server writes it to a file and responds with the file md5 hash using md5_file
. The md5 on the client is not matching what is returned by the server and I’m not sure why…
The JS library to generate the md5 is crypto-js
, we’re using jquery so expect to see $
and jquery methods on html elements.
The client code is:
/** This accepts the file from either the file input or a paste event, then resizes it
using the canvas to a max of 2500x2500px while maintaing aspect ratio. The base64 data is
then set at the img src attribute and then an html element is added to the imageContainer */
function handleImage(file, imageContainer) {
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.src = event.target.result;
img.onload = function() {
const maxDimension = 2500;
let width = img.width;
let height = img.height;
// Resize the image if it exceeds the maximum dimensions
if (width > maxDimension || height > maxDimension) {
if (width > height) {
width = maxDimension;
height = (maxDimension * img.height) / img.width;
} else {
height = maxDimension;
width = (maxDimension * img.width) / img.height;
}
}
// Create a canvas element to draw the resized image
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// Convert the canvas to a data URL
const resizedDataUrl = canvas.toDataURL(file.type);
const imageType = file.type.split("/")[1];
const imagePreview = $("<div class='image-preview'><div class='loadingIcon' style='display:none'></div><img src='" + resizedDataUrl + "' data-type='" + imageType + "'><span class='remove-image'><i class='far fa-times'></i></span></div>");
imageContainer.append(imagePreview);
imagePreview.find(".remove-image").click(function() {
imagePreview.remove();
});
};
};
reader.readAsDataURL(file);
}
...
/** Called during the submit event, grabs the base64 image data and the filetype, generates an md5 using only the image data, and then sends to the serve. */
function uploadImages(img) {
const imgData = img.attr('src')
const imgHash = CryptoJS.MD5(imgData.replace(/^data:image/(w*);base64,/, ""));
const imgType = img.data('type')
$.ajax({
url: 'files',
data: { file: imgData, fileType: imgType},
method: "POST",
dataType: "JSON",
success: function (r) {
container.find('.loadingIcon').hide();
// TODO: These aren't matching, need to figure out why...
console.log(r.md5, imgHash.toString())
}
})
}
The server code is:
<?
// this accepts the file data from the $_POST body, strips out the base64 header, and
// then writes the file. Then uses the md5_file function to generate the md5 and
// echos it back.
$file = $_POST['file'];
$fileType = $_POST['fileType'] ?? 'jpg';
$targetPath = "/uploads/files/" . uniqid() . "." . $fileType;;
$data = explode( ',', $file )[1]; // strip out the base64 header
$imageData = base64_decode($data);
file_put_contents($targetPath, $imageData);
echo md5_file($targetPath);
?>
Note that the code is simplified for brevity.
But the md5 I generate on the client is not matching the md5 returned by the server.
Does anyone see what I am doing wrong?
Thanks!
2
Answers
In js you are only hashing a base64 string while in php you are hashing a real file. These 2 things are completely different so it makes sense. if you want it to be the same you have to hash the buffer on js
The code in JS does the MD5 of a base64 string:
But the code in PHP does not:
Try doing: