Якщо у мене є файли на Amazon S3, який найпростіший спосіб отримати їх md5sum без необхідності завантажувати файли?
Дякую
Якщо у мене є файли на Amazon S3, який найпростіший спосіб отримати їх md5sum без необхідності завантажувати файли?
Дякую
Відповіді:
Документація AWS ETag
говорить:
Тег сутності - це хеш об'єкта. ETag відображає зміни лише щодо вмісту об’єкта, а не його метаданих. ETag може бути або не бути дайджестом MD5 даних об’єкта. Чи це так, залежить від того, як був створений об'єкт і як він зашифрований, як описано нижче:
- Об'єкти, створені за допомогою об'єкта PUT, об'єкта POST або копіювання, або за допомогою консолі керування AWS і зашифровані за допомогою SSE-S3 або відкритого тексту, мають теги ET, які є дайджестом MD5 даних їх об'єктів.
- Об'єкти, створені за допомогою об'єкта PUT, об'єкта POST або копіювання, або за допомогою консолі управління AWS і зашифровані за допомогою SSE-C або SSE-KMS, мають теги ET, які не є дайджестом MD5 даних їх об'єктів.
- Якщо об’єкт створюється за допомогою операції багатозавантажного завантаження або копіювання частин, ETag не є дайджестом MD5, незалежно від методу шифрування.
Посилання: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html
Здається, ETag не є MD5 для багатодольних завантажень (згідно з коментарем Гаеля Фрейтера). У цих випадках він містить суфікс мінус і число. Однак навіть біт перед мінусом не здається MD5, хоча він такий же довжини, як MD5. Можливо, суфікс - це кількість завантажених частин?
aws s3 cp ...
Нижче це робота для мене, щоб порівняти локальну контрольну суму файлу з s3 etag. Я використовував Python
def md5_checksum(filename):
m = hashlib.md5()
with open(filename, 'rb') as f:
for data in iter(lambda: f.read(1024 * 1024), b''):
m.update(data)
return m.hexdigest()
def etag_checksum(filename, chunk_size=8 * 1024 * 1024):
md5s = []
with open(filename, 'rb') as f:
for data in iter(lambda: f.read(chunk_size), b''):
md5s.append(hashlib.md5(data).digest())
m = hashlib.md5(b"".join(md5s))
print('{}-{}'.format(m.hexdigest(), len(md5s)))
return '{}-{}'.format(m.hexdigest(), len(md5s))
def etag_compare(filename, etag):
et = etag[1:-1] # strip quotes
print('et',et)
if '-' in et and et == etag_checksum(filename):
return True
if '-' not in et and et == md5_checksum(filename):
return True
return False
def main():
session = boto3.Session(
aws_access_key_id=s3_accesskey,
aws_secret_access_key=s3_secret
)
s3 = session.client('s3')
obj_dict = s3.get_object(Bucket=bucket_name, Key=your_key)
etag = (obj_dict['ETag'])
print('etag', etag)
validation = etag_compare(filename,etag)
print(validation)
etag_checksum(filename, chunk_size=8 * 1024 * 1024)
return validation
your_key
саме, що і filename
?
etag_checksum
функції я зіткнувся з проблемою з файлами, меншими тоді chunkt_size
. Простий тест розміру файлу ( os.path.getsize(filename) < chunk_size
) це виправив, якщо у когось ще є така проблема. У такому випадку хешhashlib.md5(f.read())
Це дуже давнє запитання, але мені було важко знайти інформацію нижче, і це одне з перших місць, яке я міг знайти, тому я хотів би детально розказати його на випадок, якщо комусь буде потрібно.
ETag - це MD5. Але для файлів, що завантажуються з декількох частин, MD5 обчислюється на основі об’єднання MD5 кожної завантаженої частини. Тому вам не потрібно обчислювати MD5 на сервері. Просто отримайте ETag, і все.
Як сказав у цьому @EmersonFarrugia відповіді:
Скажімо, ви завантажили файл розміром 14 МБ, а розмір вашої частини - 5 МБ. Обчисліть 3 контрольні суми MD5, що відповідають кожній частині, тобто контрольну суму перших 5 МБ, других 5 МБ та останніх 4 МБ. Потім візьміть контрольну суму їх об’єднання. Оскільки контрольні суми MD5 - це шістнадцяткове представлення двійкових даних, просто переконайтеся, що ви взяли MD5 декодованої двійкової конкатенації, а не кодованої ASCII або UTF-8. Після цього додайте дефіс і кількість частин, щоб отримати ETag.
Тож єдине, що вам потрібно, це ETag та розмір частини для завантаження. Але ETag має суфікс -NumberOfParts. Таким чином, ви можете розділити розмір на суфікс і отримати розмір частини. 5 Мб - це мінімальний розмір деталі та значення за замовчуванням. Розмір деталі повинен бути цілим числом, тому ви не можете отримати такі речі, як 7,25Mb кожного розміру деталі. Тож отримати інформацію про розмір деталі має бути легко.
Ось сценарій, щоб зробити це в osx, з версією Linux у коментарях: https://gist.github.com/emersonf/7413337
Я залишу обидва сценарії тут на випадок, якщо наведена сторінка більше не буде доступною в майбутньому:
Версія Linux:
#!/bin/bash
set -euo pipefail
if [ $# -ne 2 ]; then
echo "Usage: $0 file partSizeInMb";
exit 0;
fi
file=$1
if [ ! -f "$file" ]; then
echo "Error: $file not found."
exit 1;
fi
partSizeInMb=$2
fileSizeInMb=$(du -m "$file" | cut -f 1)
parts=$((fileSizeInMb / partSizeInMb))
if [[ $((fileSizeInMb % partSizeInMb)) -gt 0 ]]; then
parts=$((parts + 1));
fi
checksumFile=$(mktemp -t s3md5.XXXXXXXXXXXXX)
for (( part=0; part<$parts; part++ ))
do
skip=$((partSizeInMb * part))
$(dd bs=1M count=$partSizeInMb skip=$skip if="$file" 2> /dev/null | md5sum >> $checksumFile)
done
etag=$(echo $(xxd -r -p $checksumFile | md5sum)-$parts | sed 's/ --/-/')
echo -e "${1}\t${etag}"
rm $checksumFile
Версія OSX:
#!/bin/bash
if [ $# -ne 2 ]; then
echo "Usage: $0 file partSizeInMb";
exit 0;
fi
file=$1
if [ ! -f "$file" ]; then
echo "Error: $file not found."
exit 1;
fi
partSizeInMb=$2
fileSizeInMb=$(du -m "$file" | cut -f 1)
parts=$((fileSizeInMb / partSizeInMb))
if [[ $((fileSizeInMb % partSizeInMb)) -gt 0 ]]; then
parts=$((parts + 1));
fi
checksumFile=$(mktemp -t s3md5)
for (( part=0; part<$parts; part++ ))
do
skip=$((partSizeInMb * part))
$(dd bs=1m count=$partSizeInMb skip=$skip if="$file" 2>/dev/null | md5 >>$checksumFile)
done
echo $(xxd -r -p $checksumFile | md5)-$parts
rm $checksumFile
Для тих, хто витрачає час на пошуки, щоб з’ясувати, чому md5 не такий, як ETag у S3.
ETag буде обчислювати відносно патрона даних і конкатувати всі md5hash, щоб знову зробити хеш md5 і зберегти кількість фрагментів в кінці.
Ось версія C # для генерування хешу
string etag = HashOf("file.txt",8);
вихідний код
private string HashOf(string filename,int chunkSizeInMb)
{
string returnMD5 = string.Empty;
int chunkSize = chunkSizeInMb * 1024 * 1024;
using (var crypto = new MD5CryptoServiceProvider())
{
int hashLength = crypto.HashSize/8;
using (var stream = File.OpenRead(filename))
{
if (stream.Length > chunkSize)
{
int chunkCount = (int)Math.Ceiling((double)stream.Length/(double)chunkSize);
byte[] hash = new byte[chunkCount*hashLength];
Stream hashStream = new MemoryStream(hash);
long nByteLeftToRead = stream.Length;
while (nByteLeftToRead > 0)
{
int nByteCurrentRead = (int)Math.Min(nByteLeftToRead, chunkSize);
byte[] buffer = new byte[nByteCurrentRead];
nByteLeftToRead -= stream.Read(buffer, 0, nByteCurrentRead);
byte[] tmpHash = crypto.ComputeHash(buffer);
hashStream.Write(tmpHash, 0, hashLength);
}
returnMD5 = BitConverter.ToString(crypto.ComputeHash(hash)).Replace("-", string.Empty).ToLower()+"-"+ chunkCount;
}
else {
returnMD5 = BitConverter.ToString(crypto.ComputeHash(stream)).Replace("-", string.Empty).ToLower();
}
stream.Close();
}
}
return returnMD5;
}
Я виявив, що s3cmd має опцію --list-md5, яку можна використовувати з командою ls, наприклад
s3cmd ls --list-md5 s3://bucket_of_mine/
Сподіваюся, це допомагає.
Я перепроверив jets3t та консоль управління щодо завантажених файлів MD5sum, і ETag, здається, дорівнює MD5sum. Ви можете просто переглянути властивості файлу на консолі керування AWS:
Найпростішим способом було б встановити контрольну суму самостійно як метадані, перш ніж завантажувати ці файли у свій сегмент:
ObjectMetadata md = new ObjectMetadata();
md.setContentMD5("foobar");
PutObjectRequest req = new PutObjectRequest(BUCKET, KEY, new File("/path/to/file")).withMetadata(md);
tm.upload(req).waitForUploadResult();
Тепер ви можете отримати доступ до цих метаданих, не завантажуючи файл:
ObjectMetadata md2 = s3Client.getObjectMetadata(BUCKET, KEY);
System.out.println(md.getContentMD5());
Це працює для мене. У PHP ви можете порівняти контрольну суму між локальним файлом і файлом Amazon, використовуючи це:
// get localfile md5
$checksum_local_file = md5_file ( '/home/file' );
// compare checksum between localfile and s3file
public function compareChecksumFile($file_s3, $checksum_local_file) {
$Connection = new AmazonS3 ();
$bucket = amazon_bucket;
$header = $Connection->get_object_headers( $bucket, $file_s3 );
// get header
if (empty ( $header ) || ! is_object ( $header )) {
throw new RuntimeException('checksum error');
}
$head = $header->header;
if (empty ( $head ) || !is_array($head)) {
throw new RuntimeException('checksum error');
}
// get etag (md5 amazon)
$etag = $head['etag'];
if (empty ( $etag )) {
throw new RuntimeException('checksum error');
}
// remove quotes
$checksumS3 = str_replace('"', '', $etag);
// compare md5
if ($checksum_local_file === $checksumS3) {
return TRUE;
} else {
return FALSE;
}
}
Ось код для отримання S3 ETag для об’єкта в PowerShell, перетвореного з c #.
function Get-ETag {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Path,
[Parameter(Mandatory=$true)]
[int]$ChunkSizeInMb
)
$returnMD5 = [string]::Empty
[int]$chunkSize = $ChunkSizeInMb * [Math]::Pow(2, 20)
$crypto = New-Object System.Security.Cryptography.MD5CryptoServiceProvider
[int]$hashLength = $crypto.HashSize / 8
$stream = [System.IO.File]::OpenRead($Path)
if($stream.Length -gt $chunkSize) {
$chunkCount = [int][Math]::Ceiling([double]$stream.Length / [double]$chunkSize)
[byte[]]$hash = New-Object byte[]($chunkCount * $hashLength)
$hashStream = New-Object System.IO.MemoryStream(,$hash)
[long]$numBytesLeftToRead = $stream.Length
while($numBytesLeftToRead -gt 0) {
$numBytesCurrentRead = [int][Math]::Min($numBytesLeftToRead, $chunkSize)
$buffer = New-Object byte[] $numBytesCurrentRead
$numBytesLeftToRead -= $stream.Read($buffer, 0, $numBytesCurrentRead)
$tmpHash = $crypto.ComputeHash($buffer)
$hashStream.Write($tmpHash, 0, $hashLength)
}
$returnMD5 = [System.BitConverter]::ToString($crypto.ComputeHash($hash)).Replace("-", "").ToLower() + "-" + $chunkCount
}
else {
$returnMD5 = [System.BitConverter]::ToString($crypto.ComputeHash($stream)).Replace("-", "").ToLower()
}
$stream.Close()
$returnMD5
}
Ось код для отримання хешу MD5 відповідно до 2017 року
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Base64;
public class GenerateMD5 {
public static void main(String args[]) throws Exception{
String s = "<CORSConfiguration> <CORSRule> <AllowedOrigin>http://www.example.com</AllowedOrigin> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedMethod>DELETE</AllowedMethod> <AllowedHeader>*</AllowedHeader> <MaxAgeSeconds>3000</MaxAgeSeconds> </CORSRule> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedHeader>*</AllowedHeader> <MaxAgeSeconds>3000</MaxAgeSeconds> </CORSRule> </CORSConfiguration>";
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(s.getBytes());
byte[] digest = md.digest();
StringBuffer sb = new StringBuffer();
/*for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}*/
System.out.println(sb.toString());
StringBuffer sbi = new StringBuffer();
byte [] bytes = Base64.encodeBase64(digest);
String finalString = new String(bytes);
System.out.println(finalString);
}
}
Коментований код - це те, де більшість людей помиляються, змінюючи його на шістнадцятковий