记几个最近写代码遇到的几个坑。

  1. 虽然Windows Api会对 C:\\Windows\\  这种双斜杠的路径做兼容处理,但是Explorer.exe并不会,在地址栏中输入C:\\这种路径是会报错的,所以explorer.exe /e,/select,文件路径 的时候一定要做下处理。

  2. 这个算是自己的坑,多线程\线程池的时候不要直接CPUCount * 2 - 1,一定要判断一下最后的的值再说。

  3. DLL HiJack可能会被杀软影响运行。

Delphi 使用Windows API(WinCrypt)计算文件MD5哈希,支持大文件

Delphi_WinApi_GetFileHash4MD5.jpg/


Delphi 默认没有WinCrypt相关函数的定义所以引用JwaWinCrypt{jedi-apilib}单元

将 CryptCreateHash(hProv, CALG_MD5,0, 0, hHash)中的参数CALG_MD5

修改为CALG_SHA1即为计算SHA1哈希 值得注意的时CALG_SHA_256,CALG_SHA_384,CALG_SHA_512

着三个算法是在Windows XP SP3才开始支持的  XP SP2~ Win2000是不支持的!!


program Project2;

{$APPTYPE CONSOLE}

uses
  Winapi.Windows,
  System.SysUtils,
  System.Classes,
  System.Math,
  JwaWinCrypt;


Function GetFileSizeEx(hFile: THandle; Var lpFileSizeHigh :UInt64):Boolean; stdcall; external kernel32 name 'GetFileSizeEx';


Function GetFileHash4Md5(FileDirectory :PChar):String;
Const
  Buffer_Threshold = 1024 * 1024;
Label OnFail;
Var
  hFile      :THandle;
  hMapFile   :THandle;
  dwFileSize :UInt64;
  dwFileSizeH:DWORD;

  hProv      :HCRYPTPROV;
  hHash      :HCRYPTHASH;
  iIndex     :UInt64;
  dwBufSize  :DWORD;
  lpBuffer   :PByte;

  lpHash     :Array [0..MAXCHAR] Of Byte;
  dwHashLen  :DWORD;
  szHash     :Array [0..MAXCHAR] Of Char;
begin
  Writeln('文件:', FileDirectory);
  Result   := '';
  lpBuffer := Nil;
  hMapFile := INVALID_HANDLE_VALUE;
  hFile    := CreateFile(FileDirectory, GENERIC_READ, FILE_SHARE_READ, Nil,OPEN_EXISTING, 0, 0);
  if hFile = INVALID_HANDLE_VALUE then
  begin
    Writeln('CreateFile Error, ErrorCode:', GetLastError);
    Goto OnFail;
  end;

  if Not GetFileSizeEx(hFile, dwFileSize) then
  begin
    Writeln('GetFileSizeEx Error, ErrorCode:', GetLastError);
    Goto OnFail;
  end;
  Writeln('大小:', dwFileSize, ' 字节');

  hMapFile := CreateFileMapping(hFile, Nil, PAGE_READONLY, 0, 0, Nil);
  if hMapFile = INVALID_HANDLE_VALUE then
  begin
    Writeln('CreateFileMapping Error, ErrorCode:', GetLastError);
    Goto OnFail;
  end;

  if Not CryptAcquireContext(hProv, Nil, Nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT Or CRYPT_MACHINE_KEYSET) Then
  begin
    Writeln('CryptAcquireContext Error, ErrorCode:', GetLastError);
    Goto OnFail;
  end;

  if Not CryptCreateHash(hProv, CALG_MD5,0, 0, hHash) Then
  begin
    Writeln('CryptCreateHash Error, ErrorCode:', GetLastError);
    Goto OnFail;
  end;

  iIndex := 0;
  while iIndex < dwFileSize do
  begin
    dwBufSize := Min(dwFileSize - iIndex, Buffer_Threshold);
    lpBuffer  := MapViewOfFile(hMapFile, FILE_MAP_READ, Int64Rec(iIndex).Hi, Int64Rec(iIndex).Lo, dwBufSize);
    if lpBuffer = Nil then
    begin
      Writeln('MapViewOfFile Error, ErrorCode:', GetLastError);
      Goto OnFail;
    end;

    if Not CryptHashData(hHash, lpBuffer, dwBufSize, 0) then
    begin
      Writeln('CryptHashData Error, ErrorCode:', GetLastError);
      Goto OnFail;
    end;

    UnmapViewOfFile(lpBuffer);
    Inc(iIndex, Buffer_Threshold);
  end;

  dwBufSize := SizeOf(DWORD);
  dwHashLen := 0;
  if CryptGetHashParam(hHash, HP_HASHSIZE, @dwHashLen, dwBufSize, 0) then
  begin
    ZeroMemory(@lpHash, SizeOf(lpHash));
    if CryptGetHashParam(hHash, HP_HASHVAL, @lpHash, dwHashLen, 0) Then
    begin
      for dwFileSizeH := 0 to dwHashLen-1 do
      begin
        wsprintf(@szHash, '%s%02x', szHash, lpHash[dwFileSizeH]);
      end;
      Writeln('MD5:', String(szHash));
    end Else
    begin
      Writeln('Error getting hash value, ErrorCode:', GetLastError);
      Goto OnFail;
    end;  
  end Else
  begin
    Writeln('Error getting hash length value, ErrorCode:', GetLastError);
    Goto OnFail;
  end;

OnFail:
  CryptDestroyHash(hHash);
  CryptReleaseContext(hProv, 0);
  UnmapViewOfFile(lpBuffer);
  CloseHandle(hFile);
  CloseHandle(hMapFile);
end;


begin
  GetFileHash4Md5('E:\ISO\cn_windows_server_2016_updated_feb_2018_x64_dvd_11636703.iso');
  Readln;
end.


一个Let's Encrypt SSL证书的一键脚本{扣自LNMP}

代码扣自 LNMP 和 vpser的 acme.sh


用法:以dnspod为示例

先把dnspod的token 设置环境变量

# export DP_Id="你的Token ID" && export DP_Key="你的Token Key"


随后执行脚本然后一路根据提示操作即可

# ./cert dp


#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

# Check if user is root
if [ $(id -u) != "0" ]; then
	echo "Error: You must be root to run this script!"
	exit 1
fi

cert_path=$(pwd)
cert_path="$cert_path/ssl_cert"

echo "+-------------------------------------------+"
echo "|    Let's Encrypt SSL Certificate issue    |"
echo "|           By:7xCode                       |"
echo "|               https://www.7xcode.com      |"
echo "+-------------------------------------------+"

arg1=$1

Color_Text()
{
  echo -e " \e[0;$2m$1\e[0m"
}

Echo_Red()
{
  echo $(Color_Text "$1" "31")
}

Echo_Green()
{
  echo $(Color_Text "$1" "32")
}

Echo_Yellow()
{
  echo -n $(Color_Text "$1" "33")
}

Echo_Blue()
{
  echo $(Color_Text "$1" "34")
}

Sleep_Sec()
{
	seconds=$1
	while [ "${seconds}" -ge "0" ];do
	  echo -ne "\r	 \r"
	  echo -n ${seconds}
	  seconds=$(($seconds - 1))
	  sleep 1
	done
	echo -ne "\r"
}

Install_Check_Acme.sh()
{
	if [ -s /usr/local/acme.sh/acme.sh ]; then
		echo "/usr/local/acme.sh/acme.sh [found]"
	else
		cd /tmp
		[[ -f latest.tar.gz ]] && rm -f latest.tar.gz
		wget https://soft.vpser.net/lib/acme.sh/latest.tar.gz --prefer-family=IPv4 --no-check-certificate
		tar zxf latest.tar.gz
		cd acme.sh-*
		./acme.sh --install --log --home /usr/local/acme.sh --certhome ${cert_path}
		cd ..
		rm -f latest.tar.gz
		rm -rf acme.sh-*
		sed -i 's/cat "\$CERT_PATH"$/#cat "\$CERT_PATH"/g' /usr/local/acme.sh/acme.sh
		if command -v yum >/dev/null 2>&1; then
			service crond restart
			chkconfig crond on
		elif command -v apt-get >/dev/null 2>&1; then
			/etc/init.d/cron restart
			update-rc.d cron defaults
		fi
	fi

	. "/usr/local/acme.sh/acme.sh.env"
}

Add_SSL_Info_Menu()
{
	domain=""
	while :;do
		Echo_Yellow "Please enter domain(example: 7xcode.com): "
		read domain
		if [ "${domain}" != "" ]; then
			echo " Your domain: ${domain}"
			break
		else
			Echo_Red "Domain name can't be empty!"
		fi
	done

	Echo_Yellow "Enter more domain name(example: www.7xcode.com blog.7xcode.com *.7xcode.com): "
	read moredomain
	if [ "${moredomain}" != "" ]; then
		echo " domain list: ${moredomain}"
	fi
}

Add_Dns_SSL()
{
	provider=$1
	if [ "${provider}" != "" ]; then
		dns_provider="dns_${provider}"
	else
		Echo_Red "The dns manual mode can not renew automatically, you must renew it manually."
	fi

	Install_Check_Acme.sh

	if [[ ! -s /usr/local/acme.sh/dnsapi/dns_${provider}.sh && "${provider}" != "" ]]; then
		echo "DNS Provider: ${provider} not found."
		exit 1
	fi
	Add_SSL_Info_Menu

	if [ ! -d "${cert_path}" ]; then
			echo "Create a certificate store root directory"
			mkdir -p "${cert_path}"
	fi

	letsdomain=""
	if [ "${moredomain}" != "" ]; then
		letsdomain="-d ${domain}"
		for i in ${moredomain};do
			letsdomain=${letsdomain}" -d ${i}"
		done
	else
		letsdomain="-d ${domain}"
	fi

	if echo "${letsdomain}" | grep -q '\*\.' && echo "${letsdomain}" | grep -qi 'www\.'; then
		Echo_Red "wildcard SSL certificate DO NOT allow add www. subdomain."
		exit 1
	fi

	echo "Starting create SSL Certificate use Let's Encrypt..."
	if [ "${provider}" != "" ]; then
		/usr/local/acme.sh/acme.sh --issue ${letsdomain} --dns ${dns_provider}
		lets_status=$?
	else
		/usr/local/acme.sh/acme.sh --issue ${letsdomain} --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
		Echo_Yellow "Please add the above TXT record to the domain in 180 seconds!!!"
		echo
		Sleep_Sec 180
		/usr/local/acme.sh/acme.sh --renew ${letsdomain} --yes-I-know-dns-manual-mode-enough-go-ahead-please
		lets_status=$?
	fi
	if [ "${lets_status}" = 0 ] || [[ "${provider}" = "" && "${lets_status}" = 1 ]]; then
		Echo_Green "Let's Encrypt SSL Certificate create successfully."
	else
		Echo_Red "Let's Encrypt SSL Certificate create failed!"
	fi
}


if [ "${arg1}" != "" ]; then
	Add_Dns_SSL ${arg1}
else
	echo "Usage: cert {cx|ali|cf|dp|he|gd|aws}"
fi

exit


Windows 10 LTSC / Server 2016 (Server 2019 ?) 安装WSL(Linux子系统)

以管理员身份打开PowerShell并执行:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

命令执行后重启系统


下载文件:  https://aka.ms/wsl-ubuntu-1804

下载完成后将文件名修改为Ubuntu.zip 并解压到你指定的目录

PowerShell命令解压ZIP

Expand-Archive Ubuntu.zip Ubuntu


以管理员身份打开PowerShell并执行:

$userenv = [System.Environment]::GetEnvironmentVariable("Path", "User")
[System.Environment]::SetEnvironmentVariable("PATH", $userenv + "D:\Ubuntu", "User")

将命令中的Ubuntu目录修改成你的并执行将目录加入当前用户的环境变量中

然后执行命令

.\ubuntu1804.exe

16.04 为 ubuntu1604.exe  18.04 为  ubuntu1804.exe 其他发行版自行查看对应的.exe可执行文件

然后按提示输入用户名和密码就可以了


如果要默认使用root用户登录,以管理员身份打开PowerShell并执行:

ubuntu config --default-user root


AES CBC的一个需要注意的地方

使用AES的CBC模式加密时会传入一个16字节的缓冲区作为初始向量(Initialization Vector)
然后这个东西加密时传入的时什么,那么解密的时候也要传入相对于的值,不然解密后的数据前16字节会乱码。

一般情况下大家都是传入16个00作为初始向量

但是有些情况下会传入非00值,笔者也是在做代码对接时才遇到这个问题。

如果不是被资深大佬的同事点到这个估计还要debug半天。。