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!
CodePudding user response:
The code in JS does the MD5 of a base64 string:
const imgHash = CryptoJS.MD5(imgData.replace(/^data:image\/(\w*);base64,/, ""));
But the code in PHP does not:
$data = explode( ',', $file )[1]; // strip out the base64 header
$imageData = base64_decode($data);
file_put_contents($targetPath, $imageData);
echo md5_file($targetPath);
Try doing:
echo md5($imageData);
CodePudding user response:
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