본문 바로가기
카테고리 없음

해시 값을 이용해 문자열 변경 사항 확인하기

by klm hyeon woo 2024. 12. 7.

목차

· 글을 시작하며

· 처음에는 어떻게 시도했을까?

· 어떻게 변경을 했을까?


글을 시작하며

백오피스에서 위협 룰에 대한 코드 문법 기능을 위해 에디터를 탑재해야하던 순간이 있었어요.

코드 문법 기능을 통해 에디터를 찾던 도중 우아콘 컨퍼런스에서 들었던 에디터를 사용해서 코드에 대한 변경사항에 따른 예외 처리를 진행해줘야했어요. 변경에 따른 예외처리 상황은 아래와 같이 설명할 수 있을 것 같아요.

  • 코드를 작성하고, 올바른 코드일 경우 문법 검사가 통과되어야하는 조건
  • 문법 검사를 통과 후 코드를 수정할 경우, 다시 문법 검사를 하게 해야하는 조건
  • 코드를 수정했지만, 이전 통과한 코드들에 대해서 문법 검사를 다시 실행하지 않고 내부 값으로 저장하고 있다가 해당 값을 재활용하는 조건

처음에는 어떻게 시도했을까?

예전 프로젝트를 진행하면서 문자가 변경되었는지에 대한 유무를 판별하는 경험을 하긴 했지만, 이 방법이 그다지 효율적인 방법은 아니라고 생각을 했어요. 이를테면 아래와 같은 코드와 같이 비교를 진행했었습니다.

const firstArray = [1, 2, 3];
const secondArray = [1, 2, 3];
 
const isEqualArray = (firstArray, secondArray) => {
    if (firstArray.length !== secondArray.length) return false;
    return firstArray.every((val, idx) => val === secondArray[idx]);
}
 
console.log(isEqualArray(firstArray, secondArray);

위와 같이 every 메서드를 통해 비교를 하게 되면 하나라도 조건에 부합하지 않는다면 연산을 멈추고, 도중에 false를 반환하고, 그렇지 않으면 true를 반환해요. every 메서드로 구현을 했을 때는 구현에 보다 직관적이고, 배열의 각 요소를 순회하며 비교를 하기 때문에 코드가 명확하다는 장점을 가지고 있습니다.

 

다만 문자열이 길어질수록 비교 시간이 증가한다는 단점을 가지고 있는데, 긴 문자열 일수록 각 문자를 하나씩 비교해야하므로 성능이 저하될 수 있어요. 단순 비교만을 하는 것이 아닌, 이전 통과한 코드들에 대해서도 로그를 가지고 있어야 하기 때문에, 이 방법으로는 어느정도 해결에 제약이 있다고 생각을 했어요.

 

어떻게 변경을 했을까?

긴 문자열을 배열로 담고 있으면, 내부적으로 계속 포함 관계를 판별할 때 비교하기가 힘들다는 단점을 가지고 있기 때문에, 이를 해시 값으로 가지고 해시 값을 통해 비교하는 것이 어떨까라는 생각을 했어요. 그래서 간단한 FNV-1a 알고리즘을 이용해 문자열을 8자의 해시 값으로 변환해주는 함수를 만들었어요.

/**
 * @description FNV-1a 알고리즘을 통한 해시 변환 유틸 함수입니다.
 * @example
 * const hash = simpleHash('Hello BackOffice');
 * console.log(hash); // c88adc38
 * @param {string} value
 * */
const getSimpleHash = (value) => {
  const FNV_OFFSET_BASIS = 0x811c9dc5;
  const FNV_PRIME = 0x01000193;
 
  let hash = FNV_OFFSET_BASIS;
 
  for (let i = 0; i < value.length; i++) {
    hash ^= value.charCodeAt(i);
    hash *= FNV_PRIME;
    hash >>>= 0;
  }
 
  return hash.toString(16).padStart(8, '0');
};

해시 계산 자체는 문자열을 순회하는 방법인 every()와 동일한 0(n) 시간이 걸릴 수 있어요. 하지만 변환된 해시 값은 원본 문자열보다 짧기 때문에 배열에 긴 문자열을 계속해서 가지고 연산을 하는 것보다 훨씬 효과적으로 메모리 사용량을 줄일 수 있습니다.

 

위의 해시 변환 함수는 백오피스에서 문법 검사 버튼을 누르고, 서버를 통해 검증을 통과했을 때 프론트엔드 코드를 통해 변환이 되어 로컬 배열 변수에 저장이 되어 차곡차곡 쌓여요.

const hashSet = ['c88adc38', 'x18qcc48', 'yc2qvc41']; // 문법 검사를 통해 쌓인 해쉬 셋들
const rule = 'import "pe" 
rule DisguiseWithNotepad001 {
    meta:
        description = "disguise with notepad.exe"
        author = "Auto Rule Maker"
        date = "2024-08-15"
  
    condition:
       pe.version_info["OriginalFilename"] iequals "notepad.exe" and
       filesize > 40KB and filsize < 800KB and
       pe.imports("kernel32.dll", "CreateFileA")
}'; // 사용자가 입력한 코드 (예시)
const scaledToHash = getSImpleHash(rule); // 긴 코드 문자열을 해시 값으로 변경
 
console.log(hashSet.includes(scaledToHash)); // 현재 코드 문자열이 이미 저장된 해시 셋에 포함되어있는지 확인

이런 해결 방법을 통해 아래와 같이 결과를 확인할 수 있었어요, 아래의 예시는 수정화면에 대한 예시예요. 수정의 경우 이미 통과된 문법이 들어가있기 때문에 초기 마운트 시에 통과했다는 플래그 전달을 통해 배열에 해시 값을 저장하고, 그 다음 문법 수정 후 사용자가 저장 버튼을 누르면 배열에 순차적으로 해시 값이 저장되어 만약 사용자가 수정을 하다가 이전 통과된 문법 코드와 똑같이 작성할 경우 이전 해시 값 비교를 통해 문법 검사는 자동적으로 통과함으로서 효율적으로 검사 플로우를 진행할 수 있다는 장점을 얻을 수 있답니다.

 

화면 기록 2024-12-07 오후 7.16.48.mov
7.73MB

댓글