현재 위치: > 최신 기사 목록> 업로드 된 파일에서 Hash_hmac_file ()을 사용하여 업로드 된 파일에서 보안 검사 보호를 수행하는 방법은 무엇입니까?

업로드 된 파일에서 Hash_hmac_file ()을 사용하여 업로드 된 파일에서 보안 검사 보호를 수행하는 방법은 무엇입니까?

gitbox 2025-08-27

핵심 아이디어 (높은 수준)

  1. 파일이 변조되지 않았으며 키를 아는 당사자 만 올바른 서명을 생성 할 수 있는지 확인할 수 있습니다. HMAC는 간단한 해싱 (예 : SHA256)과 달리 키를 사용하여 위조를 방지합니다.

  2. 파일 업로드를위한 올바른 프로세스는 일반적으로 다음과 같습니다.

    • 공유 비밀을 생성/배포하십시오 (또는 서버 측 개인 키 사용);

    • 클라이언트 또는 발신자는 파일의 HMAC (예 : HMAC-SHA256)를 계산하고 업로드와 함께 서명 (HTTP 헤더 또는 폼 필드)을 보냅니다.

    • 파일을 수신 한 후 서버는 동일한 키를 사용하여 HMAC를 다시 계산하고 서명을 타이밍 보안 비교 ( HASH_Equals )와 비교하고 일치 할 때 확인을 전달합니다.

  3. HTTPS를 통해 서명과 파일을 영원히 전송합니다. 서명은 HTTP에서 일반 텍스트로 도청되어 재사용됩니다.


hash_hmac_file ()을 사용합니까?

  • hash_hmac_file ($ algo, $ filename, $ key, $ raw_output = false) 파일에서 HMAC를 직접 계산하고 스트리밍 파일은 내부적으로 처리됩니다. 전체 파일을 먼저 메모리에 읽을 필요가 없으며 중간 및 큰 파일에 적합합니다.

  • 서버 측 확인을 신속하게 구현 해야하는 경우 Hash_hmac_file ()은 간결하고 효율적인 솔루션입니다. 그러나보다 복잡한 시나리오 (예 : Shard Upload)의 경우 hash_init () / hash_update () / hash_final ()을 사용하여 블록 바이 블록 계산을 구현할 수도 있습니다.


기본 예 : 클라이언트 서명 + 서버 확인 (단일 파일)

다음은 일반적인 상황을 보여줍니다. 클라이언트는 공유 키를 사용하여 파일에서 HMAC를 수행하고 (클라이언트 예제는 생략 됨) 서버는 Hash_hmac_file ()을 사용하여 $ _files 업로드를 확인합니다.

서버 수신 종료 verify_upload.php (간단한 예) :

 <span><span><span class="hljs-meta">&lt;?php</span></span><span>
</span><span><span class="hljs-comment">// verify_upload.php</span></span><span>

</span><span><span class="hljs-comment">// 1) 보안 키 저장(예에서 환경 변수를 사용하십시오)</span></span><span>
</span><span><span class="hljs-variable">$HMAC_KEY</span></span><span> = </span><span><span class="hljs-title function_ invoke__">getenv</span></span><span>(</span><span><span class="hljs-string">'UPLOAD_HMAC_KEY'</span></span><span>); </span><span><span class="hljs-comment">// 배포 중에 안전하게 설정하십시오(환경 변수 / Vault)</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-variable">$HMAC_KEY</span></span><span>) {
    </span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">500</span></span><span>);
    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Server misconfiguration."</span></span><span>;
    </span><span><span class="hljs-keyword">exit</span></span><span>;
}

</span><span><span class="hljs-comment">// 2) 클라이언트가 서명을 입력한다고 가정하십시오 HTTP Header: X-File-Signature</span></span><span>
</span><span><span class="hljs-variable">$clientSig</span></span><span> = </span><span><span class="hljs-keyword">isset</span></span><span>(</span><span><span class="hljs-variable">$_SERVER</span></span><span>[</span><span><span class="hljs-string">'HTTP_X_FILE_SIGNATURE'</span></span><span>]) ? </span><span><span class="hljs-variable">$_SERVER</span></span><span>[</span><span><span class="hljs-string">'HTTP_X_FILE_SIGNATURE'</span></span><span>] : </span><span><span class="hljs-literal">null</span></span><span>;
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-variable">$clientSig</span></span><span>) {
    </span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">400</span></span><span>);
    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Missing signature."</span></span><span>;
    </span><span><span class="hljs-keyword">exit</span></span><span>;
}

</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-keyword">isset</span></span><span>(</span><span><span class="hljs-variable">$_FILES</span></span><span>[</span><span><span class="hljs-string">'file'</span></span><span>]) || </span><span><span class="hljs-variable">$_FILES</span></span><span>[</span><span><span class="hljs-string">'file'</span></span><span>][</span><span><span class="hljs-string">'error'</span></span><span>] !== UPLOAD_ERR_OK) {
    </span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">400</span></span><span>);
    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Upload failed."</span></span><span>;
    </span><span><span class="hljs-keyword">exit</span></span><span>;
}

</span><span><span class="hljs-comment">// 임시 업로드 파일 경로</span></span><span>
</span><span><span class="hljs-variable">$tmpPath</span></span><span> = </span><span><span class="hljs-variable">$_FILES</span></span><span>[</span><span><span class="hljs-string">'file'</span></span><span>][</span><span><span class="hljs-string">'tmp_name'</span></span><span>];

</span><span><span class="hljs-comment">// 3) 사용 hash_hmac_file 서버 측 서명을 계산합니다(사용 SHA256)</span></span><span>
</span><span><span class="hljs-variable">$algo</span></span><span> = </span><span><span class="hljs-string">'sha256'</span></span><span>;
</span><span><span class="hljs-variable">$serverSig</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hash_hmac_file</span></span><span>(</span><span><span class="hljs-variable">$algo</span></span><span>, </span><span><span class="hljs-variable">$tmpPath</span></span><span>, </span><span><span class="hljs-variable">$HMAC_KEY</span></span><span>);

</span><span><span class="hljs-comment">// 4) 사용 hash_equals 타이밍 안전 비교를하십시오,유출되는 정보를 피하십시오</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">hash_equals</span></span><span>(</span><span><span class="hljs-variable">$serverSig</span></span><span>, </span><span><span class="hljs-variable">$clientSig</span></span><span>)) {
    </span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">403</span></span><span>);
    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Signature mismatch. File may be tampered."</span></span><span>;
    </span><span><span class="hljs-comment">// 감사 로그를 기록 할 수 있습니다:원천 IP、파일 이름、시간과 기다림</span></span><span>
    </span><span><span class="hljs-keyword">exit</span></span><span>;
}

</span><span><span class="hljs-comment">// 5) 서명 검증이 통과되었습니다,파일을 최종 디렉토리로 안전하게 이동하고 권한을 설정하십시오.</span></span><span>
</span><span><span class="hljs-variable">$dest</span></span><span> = </span><span><span class="hljs-keyword">__DIR__</span></span><span> . </span><span><span class="hljs-string">'/uploads/'</span></span><span> . </span><span><span class="hljs-title function_ invoke__">basename</span></span><span>(</span><span><span class="hljs-variable">$_FILES</span></span><span>[</span><span><span class="hljs-string">'file'</span></span><span>][</span><span><span class="hljs-string">'name'</span></span><span>]);
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">move_uploaded_file</span></span><span>(</span><span><span class="hljs-variable">$tmpPath</span></span><span>, </span><span><span class="hljs-variable">$dest</span></span><span>)) {
    </span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">500</span></span><span>);
    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Failed to store file."</span></span><span>;
    </span><span><span class="hljs-keyword">exit</span></span><span>;
}

</span><span><span class="hljs-comment">// 선택 과목:서명을 저장하십시오/메타 데이터는 후속 확인을 위해 데이터베이스로 전송됩니다</span></span><span>
</span><span><span class="hljs-comment">// $db-&gt;insert('uploads', ['name'=&gt;..., 'sig'=&gt;$serverSig, ...]);</span></span><span>

</span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">200</span></span><span>);
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Upload verified and stored."</span></span><span>;
</span></span>

클라이언트는 요청과 함께 서명 값 (예 : 16 진 문자열)을 보내야합니다. 샘플 HMAC 생성 (PHP 클라이언트 또는 명령 줄 데모) :

 <span><span><span class="hljs-meta">&lt;?php</span></span><span>
</span><span><span class="hljs-comment">// client_sign.php</span></span><span>
</span><span><span class="hljs-variable">$key</span></span><span> = </span><span><span class="hljs-string">'shared-secret-key'</span></span><span>;
</span><span><span class="hljs-variable">$file</span></span><span> = </span><span><span class="hljs-string">'/path/to/file.bin'</span></span><span>;
</span><span><span class="hljs-variable">$algo</span></span><span> = </span><span><span class="hljs-string">'sha256'</span></span><span>;
</span><span><span class="hljs-variable">$sig</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hash_hmac_file</span></span><span>(</span><span><span class="hljs-variable">$algo</span></span><span>, </span><span><span class="hljs-variable">$file</span></span><span>, </span><span><span class="hljs-variable">$key</span></span><span>);
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-variable">$sig</span></span><span>; </span><span><span class="hljs-comment">// 서버로 전송되었습니다</span></span><span>
</span></span>

실제 HTTP 업로드에서 클라이언트는 X-File-Signature : <SIG> 헤더를 멀티 파트/양식 데이터 업로드 또는 양식 필드 서명 으로 보냅니다.


큰 파일/슬라이스 업로드 제안

Hash_hmac_file () 자체는 큰 파일을 처리 할 수 ​​있지만 Sharded 업로드 또는 스트리밍 업로드 시나리오에서는 HMAC를 블록별로 계산하거나 세그먼트 화 된 HMAC를 채택해야합니다.

솔루션 A : 병합 된 파일을 수신 한 후 서버는 hash_hmac_file ()을 사용하여 확인합니다.

장점 : 간단한 구현; 단점 : IO 및 디스크를 차지할 수있는 서버의 전체 파일을 임시 스토리지/병합해야합니다.

솔루션 B : 업로드 할 때 블록별로 HMAC 블록 계산 (클라이언트 및 서버 각 블록 동기화).

  • 클라이언트는 각 블록의 HMAC를 계산하고 블록 서명을 보냅니다. 서버는 각 블록을 수신 할 때 파일을 확인하고 추가합니다. 마지막 블록이 성공적으로 업로드되고 전체 파일이 확인되었습니다.

  • 또는 클라이언트는 각 블록의 원래 데이터를 보내고 끝에 전체 파일의 HMAC를 보냅니다 (클라이언트가 전체 서명을 로컬로 생성 할 수있는 경우).

솔루션 C : 스트리밍 해시 (서버 측) 사용

서버가 PHP에서 직접 업로드 스트림을 읽는 경우 (예 : PSR-7 또는 PHP : // 입력 사용) 스트림을 읽을 때 Hash_Init ( 'SHA256', HASH_HMAC, $ 키)HASH_UPDATE ()를 사용할 수 있고 마지막으로 hash_final ()을 사용할 수 있습니다. 예:

 <span><span><span class="hljs-meta">&lt;?php</span></span><span>
</span><span><span class="hljs-variable">$key</span></span><span> = </span><span><span class="hljs-title function_ invoke__">getenv</span></span><span>(</span><span><span class="hljs-string">'UPLOAD_HMAC_KEY'</span></span><span>);
</span><span><span class="hljs-variable">$algo</span></span><span> = </span><span><span class="hljs-string">'sha256'</span></span><span>;

</span><span><span class="hljs-comment">// 초기화 HMAC 문맥</span></span><span>
</span><span><span class="hljs-variable">$context</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hash_init</span></span><span>(</span><span><span class="hljs-variable">$algo</span></span><span>, HASH_HMAC, </span><span><span class="hljs-variable">$key</span></span><span>);

</span><span><span class="hljs-comment">// 우리를 가정합니다 php://input 또는 파일 스트림은 블록으로 블록으로 읽습니다</span></span><span>
</span><span><span class="hljs-variable">$stream</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fopen</span></span><span>(</span><span><span class="hljs-string">'php://input'</span></span><span>, </span><span><span class="hljs-string">'rb'</span></span><span>);
</span><span><span class="hljs-keyword">while</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">feof</span></span><span>(</span><span><span class="hljs-variable">$stream</span></span><span>)) {
    </span><span><span class="hljs-variable">$chunk</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fread</span></span><span>(</span><span><span class="hljs-variable">$stream</span></span><span>, </span><span><span class="hljs-number">8192</span></span><span>);
    </span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$chunk</span></span><span> === </span><span><span class="hljs-literal">false</span></span><span>) </span><span><span class="hljs-keyword">break</span></span><span>;
    </span><span><span class="hljs-title function_ invoke__">hash_update</span></span><span>(</span><span><span class="hljs-variable">$context</span></span><span>, </span><span><span class="hljs-variable">$chunk</span></span><span>);
}
</span><span><span class="hljs-variable">$serverSig</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hash_final</span></span><span>(</span><span><span class="hljs-variable">$context</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">fclose</span></span><span>(</span><span><span class="hljs-variable">$stream</span></span><span>);

</span><span><span class="hljs-comment">// 그런 다음 비교하십시오 clientSig ...</span></span><span>
</span></span>

재생 공격 및 관련 메타 데이터를 방지합니다

간단한 HMAC는 무결성과 인증 (키를 보유한 당사자에서 파생) 만 보장 할 수 있지만 "재생"(동일한 파일 + 서명이 다시 재생 됨). 보호 방법 :

  • 서명 할 때 파일의 타임 스탬프, Nonce (랜덤 번호), 업 로더 ID 등이 함께 서명됩니다 . 예를 들어이 메타 데이터를 Signature = HMAC (키, 파일 이름 + ":" + file_sha256 + ":" + timestamp + ":" + nonce)로 스 플라이싱하고 timestamp and nonce를 함께 보냅니다.

  • 서버는 타임 스탬프를 확인하고 (예 : ± 5 분) 재생을 방지하기 위해 NOCE가 사용되었는지 (일정 시간 동안 지속되어야하는지)를 확인합니다.

  • API의 경우 : 짧은 시간 동안 유효한 사전 서명 된 업로드 토큰을 사용하고 토큰에 만료 및 서명을 포함하십시오. 서버는 파일 HMAC를 확인하기 전에 토큰의 유효성을 확인합니다.

샘플 서명 체계 (파일의 외부 서명) :

  • 클라이언트는 먼저 단기 업로드 자격 증명을 요청합니다 (서버에서 생성 및 서명 한 Nonce 및 Expiry 포함).

  • 클라이언트는 파일을 업로드하고 자격 증명 및 파일 HMAC를 헤더에 넣습니다.

  • 서버는 먼저 자격 증명을 확인한 다음 파일 HMAC를 확인합니다.


주요 관리 및 보안 권장 사항

  1. 키 저장소 : HMAC 키는 창고/코드에서 하드 코딩되어서는 안됩니다. 환경 변수, 구성 파일의 제한된 저장 또는 특수 키 관리 시스템 (Vault, Cloud KMS)을 사용하십시오.

  2. 최소 권한 : 키의 사용 및 권한을 최소화해야합니다. 다른 키를 다른 용도로 할당 할 수있는 경우 (업로드 시그니처 키는 다른 서비스 키와 분리됩니다).

  3. 정기적 인 회전 : 키 회전 정책 (예 : 90 일마다)을 개발하고 검증 중단을 피하기 위해 기존 키 (새/오래된 키가 모두 회전하는 동안 지원)의 단기 검증을 지원합니다.

  4. 감사 : 나중에 공격 동작의 탐지를 용이하게하기 위해 요청, 소스 IP 및 실패한 서명 시간을 기록하십시오.

  5. HTTPS/TLS 사용 : 서명 및 파일을 TLS를 통해 전송하여 중개자가 서명 또는 훔치기 키를 도청하는 것을 피해야합니다.


타이밍 공격을 피하기 위해 비교를 위해 hash_equals를 사용하십시오

서명을 비교하기 위해 == , == 또는 문자열 스 플라이 싱을 사용하지 마십시오. 예를 들어 일정한 시간 비교에 hash_equals ()를 사용해야합니다.

 <span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">hash_equals</span></span><span>(</span><span><span class="hljs-variable">$serverSig</span></span><span>, </span><span><span class="hljs-variable">$clientSig</span></span><span>)) {
    </span><span><span class="hljs-comment">// 거부하다</span></span><span>
}
</span></span>

Hash_equals ()는 길이가 다르거나 조기 불일치가있을 때 시차가 악용되는 것을 방지 할 수 있습니다.


기타 보안 문제 (파일 유형, 권한) 처리

HMAC 확인은 무결성과 인증의 일부일 뿐이며 다음과 같습니다.

  • 악성 스크립트를 실행하지 않도록 파일 유형 (Mime Type + 파일 헤더 서명)을 확인하십시오 .

  • 크기 제한 , 업로드 주파수 제한 및 사용자의 크기와 유형을 화이트리스트에 올리십시오.

  • 웹 루트 디렉토리에서 사용자 업로드 디렉토리를 구분하고 적절한 파일 권한을 설정합니다 (실행 가능).

  • 실행 파일은 고위험 시나리오에서 추가 스캔 (바이러스 스캔/샌드 박스 분석)으로 수행됩니다.


고급 시나리오 : 서버 측 서명 (사전 서명 된 URL 발행)

일부 아키텍처는 서버가 클라이언트에 "Presigned URL"또는 토큰을 클라이언트에 발행하여 클라이언트가 객체 저장소 (S3, GCS 등)에 직접 업로드 할 수 있기를 원합니다. 이 시나리오에서 :

  • 서버가 사전 서명 된 URL을 생성 할 때 HMAC (또는 클라우드 스토리지 자체 서명 메커니즘을 사용)를 포함시키고 향후 업로드 할 때 특정 헤더 (예 : x-expected-sha256 )를 가져와야한다는 것을 클라이언트에게 알릴 수 있습니다.

  • 파일이 클라우드 스토리지에 업로드되면 서버는 콜백 또는 후속 검색을 통해 객체의 HMAC 또는 객체 무결성을 확인할 수 있습니다 (예 : 스토리지의 파일에 대한 HMAC를 다시 계산하거나 클라이언트가 클라우드 스토리지에서 서명을 제공하고 메타 데이터를 업로드 할 때 메타 데이터를 반환하는 것). 세부 사항은 클라우드 스토리지의 기능에 따라 다릅니다.


FAQ (짧은)

Q : HTTP가있는 경우 왜 여전히 HMAC가 필요한가?
HTTPS는 전송 보안을 보호하지만 법적 키 보유자가 악의적 인 파일을 업로드하거나 변조 후 서버 측에 다시 업로드하는 것을 방지 할 수는 없습니다. HMAC는 "키가있는 당사자 가이 업로드에서 서명을 생성하고 전송/스토리지 전후에 파일 내용이 수정되지 않도록 할 수 있습니다.

Q : hash_file () (키 없음) 만 사용할 수 있습니까?
Hash_file ()은 무결성 만 확인할 수 있지만 (변조되었는지) 누구나 해시 값을 위조 할 수 있습니다. 해시 값을 노출시키지 않고 서버 만 계산하고 비교할 책임이있는 경우 Hash_file () 도 유용합니다. 그러나 업로드 개시기를 확인하려면 HMAC를 사용해야합니다.


요약 (운영 지점)

  • 서버에서 파일 hmac을 효율적으로 계산하려면 hash_hmac_file () (또는 hash_init + hash_update )를 사용하십시오. 클라이언트는 동일한 키로 서명하고 서명을 보냅니다 (또는 사전 서명 된 자격 증명은 서버에서 발행).

  • 타이밍 안전 비교에 hash_equals ()를 사용하십시오.

  • 보안 저장 및 회전 정책을 사용하여 항상 HTTPS, 키를 통해 서명 및 파일을 전송하십시오.

  • 큰 파일/슬라이스 업로드의 경우 스트리밍 HMAC 또는 SHARD 서명 체계를 사용하고 최종 병합 또는 칩 당 검증 중에 서버 측의 일관성을 유지하십시오.

  • HMAC는 변조 방지 및 소스 검증의 일부로 사용되며 파일 유형 감지, 권한 제어 및 바이러스 스캐닝을 결합하여 완전한 보호 시스템을 형성합니다.