Step to UEFI (172)UEFI 下的 RSA (下)

上一篇介绍了 RSA 的基本原理和例子,这里介绍一下 UEFI Shell 下的实现。正规的实现方法需要使用 CyptoPkg 加 OpenSSL 源代码,但是我在查看 CyptoPkg 的时候发现目前只有 RSA 做身份认证的部分,加之 OpenSSL 使用起来很繁琐。因此,这里通过RSAEuro 库【参考1】来实现。

根据库提供的文档,编写测试代码。首先是测试通过给定的 RSA Private Key 加密,然后 Public Key 解密。再测试随机生成 Private Key然后 Public Key进行解密。

RSATest.c

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/BaseMemoryLib.h>
#include  <stdio.h>
#include  <string.h>
#include  <stdlib.h>

#include "rsaeuro/rsaeuro.h"
#include "rsaeuro/rsa.h"

static R_RSA_PUBLIC_KEY PUBLIC_KEY1 = {
	512,
	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0xc0, 0x76, 0x47, 0x97, 0xb8, 0xbe, 0xc8, 0x97,
	 0x2a, 0x0e, 0xd8, 0xc9, 0x0a, 0x8c, 0x33, 0x4d, 0xd0, 0x49, 0xad, 0xd0,
	 0x22, 0x2c, 0x09, 0xd2, 0x0b, 0xe0, 0xa7, 0x9e, 0x33, 0x89, 0x10, 0xbc,
	 0xae, 0x42, 0x20, 0x60, 0x90, 0x6a, 0xe0, 0x22, 0x1d, 0xe3, 0xf3, 0xfc,
	 0x74, 0x7c, 0xcf, 0x98, 0xae, 0xcc, 0x85, 0xd6, 0xed, 0xc5, 0x2d, 0x93,
	 0xd5, 0xb7, 0x39, 0x67, 0x76, 0x16, 0x05, 0x25},
	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01}
};

static R_RSA_PRIVATE_KEY PRIVATE_KEY1 = {
	512,
	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0xc0, 0x76, 0x47, 0x97, 0xb8, 0xbe, 0xc8, 0x97,
	 0x2a, 0x0e, 0xd8, 0xc9, 0x0a, 0x8c, 0x33, 0x4d, 0xd0, 0x49, 0xad, 0xd0,
	 0x22, 0x2c, 0x09, 0xd2, 0x0b, 0xe0, 0xa7, 0x9e, 0x33, 0x89, 0x10, 0xbc,
   0xae, 0x42, 0x20, 0x60, 0x90, 0x6a, 0xe0, 0x22, 0x1d, 0xe3, 0xf3, 0xfc,
	 0x74, 0x7c, 0xcf, 0x98, 0xae, 0xcc, 0x85, 0xd6, 0xed, 0xc5, 0x2d, 0x93,
   0xd5, 0xb7, 0x39, 0x67, 0x76, 0x16, 0x05, 0x25},
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01},
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x1a, 0xe3, 0x6b, 0x75, 0x22, 0xf6, 0x64, 0x87,
	 0xd9, 0xf4, 0x61, 0x0d, 0x15, 0x50, 0x29, 0x0a, 0xc2, 0x02, 0xc9, 0x29,
   0xbe, 0xdc, 0x70, 0x32, 0xcc, 0x3e, 0x02, 0xac, 0xf3, 0x7e, 0x3e, 0xbc,
	 0x1f, 0x86, 0x6e, 0xe7, 0xef, 0x7a, 0x08, 0x68, 0xd2, 0x3a, 0xe2, 0xb1,
   0x84, 0xc1, 0xab, 0xd6, 0xd4, 0xdb, 0x8e, 0xa9, 0xbe, 0xc0, 0x46, 0xbd,
	 0x82, 0x80, 0x37, 0x27, 0xf2, 0x88, 0x87, 0x01},
	{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x02, 0xb6, 0x15,
		0xfe, 0x15, 0x92, 0x8f, 0x41, 0xb0, 0x2b, 0x58, 0x6b, 0x51, 0xc2, 0xc0,
		0x22, 0x60, 0xca, 0x39, 0x68, 0x18, 0xca, 0x4c, 0xba, 0x60, 0xbb, 0x89,
		0x24, 0x65, 0xbe, 0x35},
	 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0xee, 0xb6, 0x0d,
		0x54, 0x35, 0x18, 0xb4, 0xac, 0x74, 0x83, 0x4a, 0x05, 0x46, 0xc5, 0x07,
		0xf2, 0xe9, 0x1e, 0x38, 0x9a, 0x87, 0xe2, 0xf2, 0xbe, 0xcc, 0x6f, 0x8c,
		0x67, 0xd1, 0xc9, 0x31}},
	{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x48, 0x7e, 0x99,
		0xe3, 0x75, 0xc3, 0x8d, 0x73, 0x21, 0x12, 0xd9, 0x7d, 0x6d, 0xe8, 0x68,
		0x7f, 0xda, 0xfc, 0x5b, 0x6b, 0x5f, 0xb1, 0x6e, 0x72, 0x97, 0xd3, 0xbd,
		0x1e, 0x43, 0x55, 0x99},
	 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0xb5, 0x50, 0xde,
		0x64, 0x37, 0x77, 0x4d, 0xb0, 0x57, 0x77, 0x18, 0xed, 0x6c, 0x77, 0x07,
		0x24, 0xee, 0xe4, 0x66, 0xb4, 0x31, 0x14, 0xb5, 0xb6, 0x9c, 0x43, 0x59,
		0x1d, 0x31, 0x32, 0x81}},
	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x4c, 0x79, 0xc4,
	 0xb9, 0xbe, 0xa9, 0x7c, 0x25, 0xe5, 0x63, 0xc9, 0x40, 0x7a, 0x2d, 0x09,
	 0xb5, 0x73, 0x58, 0xaf, 0xe0, 0x9a, 0xf6, 0x7d, 0x71, 0xf8, 0x19, 0x8c,
	 0xb7, 0xc9, 0x56, 0xb8}
};

R_RSA_PUBLIC_KEY PUBLIC_KEY2;
R_RSA_PRIVATE_KEY PRIVATE_KEY2;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
        char    message[]="www.lab-z.com!";
        char    encrypted[1024];
        char    decrypted[1024];
        int     enlen,delen;
        R_RSA_PROTO_KEY protoKey;
        int status;
        R_RSA_PUBLIC_KEY PUBLIC_KEY2;
        R_RSA_PRIVATE_KEY PRIVATE_KEY2;
        
   //  Demo for given Private and Public key
        // Encrypt message by PRIVATE_KEY1
        RSAPrivateEncrypt(encrypted,&enlen,message,sizeof(message),&PRIVATE_KEY1);
        Print(L"After encryped length [%d]\n",enlen);
        
        // Decrypt message by PUBLIC_KEY1
        RSAPublicDecrypt(decrypted,&delen,encrypted,enlen,&PUBLIC_KEY1);
        // Show decrypt result
        Print(L"decrypted [%a]\n",decrypted);
   
    //  Demo for generated Private and Public key   
        R_RANDOM_STRUCT randomStruct;
        R_RandomCreate(&randomStruct);
        
        protoKey.bits =1024;
        protoKey.useFermat4 = 1;        

        // Generate Private and Public Key
        status = R_GeneratePEMKeys(&PUBLIC_KEY2, &PRIVATE_KEY2, &protoKey, &randomStruct);
        if(status) {
                Print(L"Key Generation fail\n");
                return;
        }        
        
        // Encrypt message
        RSAPrivateEncrypt(encrypted,&enlen,message,sizeof(message),&PRIVATE_KEY2);
        Print(L"After encryped length [%d]\n",enlen);
        
        // Decrypt message
        RSAPublicDecrypt(decrypted,&delen,encrypted,enlen,&PUBLIC_KEY2);
        Print(L"decrypted [%a]\n",decrypted);   
        
  return EFI_SUCCESS;
}

RSATest.inf

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = rsat
  FILE_GUID                      = 4ea97c46-7469-4dfe-0076-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#   
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
        RSATest.c
        rsaeuro\des.h
        rsaeuro\des386.s
        rsaeuro\desc.c
        rsaeuro\global.h
        rsaeuro\md2.h
        rsaeuro\md2c.c
        rsaeuro\md4.h
        rsaeuro\md4c.c
        rsaeuro\md5.h
        rsaeuro\md5c.c
        rsaeuro\nn.c
        rsaeuro\nn.h
        rsaeuro\prime.c
        rsaeuro\prime.h
        rsaeuro\rsa.c
        rsaeuro\rsa.h
        rsaeuro\rsa386.s
        rsaeuro\rsa68k.s
        rsaeuro\rsaeuro.h
        rsaeuro\rsaref.h
        rsaeuro\rsasparc.s
        rsaeuro\r_dh.c
        rsaeuro\r_encode.c
        rsaeuro\r_enhanc.c
        rsaeuro\r_keygen.c
        rsaeuro\r_random.c
        rsaeuro\r_random.h
        rsaeuro\r_stdlib.c
        rsaeuro\shs.h
        rsaeuro\shsc.c


[Packages]
  StdLib/StdLib.dec   
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec 


[LibraryClasses]
  LibC
  LibStdio
  ShellCEntryLib   
  ShellLib

[Protocols]
	
[BuildOptions]
   MSFT:*_*_X64_CC_FLAGS        = /wd4131 /wd4244 /wd4133 /wd4005 /wd4706

运行结果:

完整代码下载:

除了 RSA算法,这个库还提供了 MD2/MD4/MD5/ SHS(Secure Hash Standard:安全杂乱信息标准) 算法,DES算法,有兴趣的朋友可以通过他的代码了解具体实现。

参考:

1. https://github.com/mort666/RSAEuro

FireBeelte 多串口通讯的实现

最近在用DFRobot 出品的 FireBeelte 做点东西玩,在使用中需要用到多个串口。当我在程序开始写上  Include softwareSerial.h 之后,编译报错。研究了一番发现目前这个平台没有支持 Software Serial (准确的说没有自带原生的库,但是看起来有几个第三方的库)。再研究一番发现还有更简单的实现。

ESP32 支持了3个硬件串口(这样算起来不支持 Software Serial 也没有什么影响)。分别位于 IO3/1 , IO9/10 和 IO16/17。换成 Arduino IDE  的语言是 D0/0, D5/6 和DI/LRCK。其中的IO3/1 已经给烧写程序使用,所以一般都是使用后面2组。

使用这些硬件串口的代码可以在\AppData\Local\Arduino15\packages\esp32\hardware\DFRobot_FireBeetle-ESP32\0.0.9\cores\esp32\HardwareSerial.h 和\AppData\Local\Arduino15\packages\esp32\hardware\DFRobot_FireBeetle-ESP32\0.0.9\cores\esp32\HardwareSerial.cpp 看到。

比如:初始化函数 begin() 函数,接收序号作为参数

HardwareSerial::HardwareSerial(int uart_nr) : _uart_nr(uart_nr), _uart(NULL) {}
void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPin, bool invert)
{
    if(0 > _uart_nr || _uart_nr > 2) {
        log_e("Serial number is invalid, please use 0, 1 or 2");
        return;
    }
    if(_uart) {
        end();
    }
    if(_uart_nr == 0 && rxPin < 0 && txPin < 0) {
        rxPin = 3;
        txPin = 1;
    }
    if(_uart_nr == 1 && rxPin < 0 && txPin < 0) {
        rxPin = 9;
        txPin = 10;
    }
    if(_uart_nr == 2 && rxPin < 0 && txPin < 0) {
        rxPin = 16;
        txPin = 17;
    }
    _uart = uartBegin(_uart_nr, baud, config, rxPin, txPin, 256, invert);
}

对用户来说使用HardwareSerial Serial1(1); 这样的就可以使用第二组串口。下面就是一个从USB发送然后转发到其他串口的例子:

HardwareSerial Serial1(2);
//HardwareSerial Serial2(2);

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);
  //Serial2.begin(115200);
}

void loop() {
  
  if (Serial.available()) {      // If anything comes in Serial (USB),
    Serial1.write(Serial.read());   // read it and send it out Serial1 (pins 0 & 1)
  }

  if (Serial1.available()) {     // If anything comes in Serial1 (pins 0 & 1)
    Serial.write(Serial1.read());   // read it and send it out Serial (USB)
  }
}

看起来实现很简单,功能也很强大,有兴趣的朋友可以研究一下。

参考:

1. https://www.qutaojiao.com/2452.html ESP32使用多个串行端口

Step to UEFI (171)UEFI 下的 RSA (上)

本文会介绍关于 RSA 算法的基础知识和一些简单的实验。行文力求通俗易懂,期望没有 UEFI 相关知识的朋友也能够读懂。文章涉及到一些密码方面的内容,是根据自己的理解进行描述,因此会存在不准确的情况,请读者在阅读过程中注意批判。

最容易的理解的密码是对称式的密码,加密解密是同样的密码。但在实际使用上,加密方如何将密码传递给解密方是一个严重的问题。比如,我设置了一个密码,然后告诉对方这是《和张仆射塞下曲》的前两句的拼音首字母“月黑雁飞高,单于夜遁逃”。对方尝试了很久都没有效果,我只能告诉他是“yhyfgdyydt”【参考1】…….当然相比对方理解出现问题,更容易出现问题的环节是密码在传递过程中被第三方截获。因此,我们在诸多谍战片中常常看到双方为了争夺密码本展开殊死搏斗。

为了克服对称式密码的缺陷,RSA横空出世。“RSA公钥加密算法是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。1987年7月首次在美国公布,当时他们三人都在麻省理工学院工作实习。RSA就是他们三人姓氏开头字母拼在一起组成的。”【参考2】

简单的说RSA 的原理就是“对两个质数相乘容易,而将其合数分解很难的这个特点进行的加密算法”【参考3】。

在使用上,RSA算法将密码分成2部分,公钥和私钥。一个典型的应用场景是:我想接受别人发给我的秘密消息,那么我可以生成一套公钥和私钥。私钥留在自己手上,公钥放在我的网站上。任何人想给我发送秘密的内容,可以先到网站获取公钥,然后用公钥加密消息,再通过 eMail之类的发送密文给我,收到消息之后,我用私钥解密然后就获得了消息明文。在这个过程中,如果有人截获了加密后的密文,在有公钥和密文的情况下仍然无法得到原文。通过这样的方式达到保密通讯的目的。

上面是对于这个算法的简单介绍,下面通过一系列实验来展示这个算法的使用。

通过在线的RSA生成工具进行实验. 在http://web.chacuo.net/netrsakeypair 生成一对公钥和私钥(这是随机生成的)

上面生成的公钥和私钥:

公钥如下
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDc5e96r+i2suNQbzBUV86tTyBW
RITmdb2Z46BaTWNlb3jxQkh3s97tI4aEwXqoZOWRGkSmSgaG/FT0ii3DAgYjKTYZ
GotxqmcshpXOJXjTnXnBKsMo2xIQeiqkYyuDD8bqlp/6XkgL1ohCkRq8rjNt85xv
MN0yzspp9HpKl/EScwIDAQAB
-----END PUBLIC KEY-----

私钥如下:
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANzl73qv6Lay41Bv
MFRXzq1PIFZEhOZ1vZnjoFpNY2VvePFCSHez3u0jhoTBeqhk5ZEaRKZKBob8VPSK
LcMCBiMpNhkai3GqZyyGlc4leNOdecEqwyjbEhB6KqRjK4MPxuqWn/peSAvWiEKR
GryuM23znG8w3TLOymn0ekqX8RJzAgMBAAECgYAClq83wNf5TB9d0e+/DUhev46h
dCwah0axhvlaFY4UojnImf4/aNwz6zaoV5wYXRZTnPsw960b59kXBIeEwYFQ4pdd
gBlRnwZ9Dv5i4an1pGvZ7MA9hlUlFpEtTJ95HL4b4WpKL+jpNTStT8dItf1FISVk
NDIwdSZFjCE0/VNAAQJBAPSeUsunMbRkz2oJRXBCkvRoxnb9gemgkC4yokCxd4So
iWBsqZDMQJ4x5RmdIbzwyFlyeBlmmDsb+P/mWsPPZ1MCQQDnLRPR860t+QCeET4G
Q9EzPDKAYjoV4wcPNrgzjXd4tiwJeh1nHhaDtYal9XEvREYhUtk6qmLMGuAnqOWm
t+RhAkEAoFtRh3OZH9qeJbLiNE9QKqysvcA987twCPjkaGhuIyagt/dDyUo8affn
ab0aKtPlYs2pcW1SCh2yQ37srUQ/RQJBAMEkvVGFmKQ3TRfDaiHL2WZIHh17c/JD
WuuQGTghMrcs5QAKAbTcw4zJRjU0KpuGHF3NHWdRYfgLYEpiZ3TyYSECQQDgMqF1
yP5L7fVGVn2tCY5aQ1nDybs0piQ6Op6GisIJ8xecPa/SSRFbdncJnwyWC0bvw4Uj
k40U/iT/Huk6faRK
-----END PRIVATE KEY-----

接下来, 去http://tool.chacuo.net/cryptrsapubkey 使用上面的公钥对 “www.lab-z.com”  这段字符串进行加密(需要把上面生成的公钥拷贝粘贴到公钥框中,然后点击RSA公钥加密):

得到密文如下:

“VtVUjww1txK9Aqrtuhm6H5QuP9JjJJb0loIG/Sq9XzVrzcak9RN8X3DdbN9E3o421vL/ROMpIlt7heKlVqxQNxiz9IToGyRU/pT/HuW+L/hK0lyG6PPITticyXeVUIloAKILiD62ELN2FpAoJcYLmEgEfKBVkkzUI1DoLBHN/uw=”

再接下来, 实验用私钥对上面的密文进行解密。到http://tool.chacuo.net/cryptrsaprikey 页面, 将最开始生成的私钥粘贴到里面, 然后粘贴上面的密文. 最后点击RSA私钥解密即可得到原文。

上面是一个完整的RSA加密解密的例子。在 RSA 中,还可以使用私钥来进行加密,加密之后的密文可以使用公钥来进行解密。因此,RSA 还可以用来进行身份验证。例如:我可以用私钥来加密一段信息,然后将密文发布在网站上。用户用公钥可以对密文进行解密。如果能够解密,就可以证明这段话确实是私钥拥有者发出来的。也正是如此,所以对于大多数自称“中本聪”而又无法用私钥来证明自己身份的人(无需Show出完整的私钥,只要公布一段私钥签名后的密文足矣),观众完全可以一笑了之。

目前业界通用的做法是使用 OpenSSL 来生成密钥对以及加密解密。OpenSSL中RSA私钥文件生成命令为:

        openssl genrsa -out private_rsa.pem  1024

生成RSA公钥命令为:

openssl rsa -in private_rsa.pem -pubout -out public_rsa.pem

      在openssl中执行以下命令,将BASE64编码的文件装换成二进制编码:

 openssl   base64  -d  -in private_rsa.pem -out private.pem

也可以直接使用OpenSSL命令以明文形式输出密钥的各个参数值,例如:

openssl rsa -in private_rsa.pem -text -out private.txt
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDc5e96r+i2suNQbzBUV86tTyBW
RITmdb2Z46BaTWNlb3jxQkh3s97tI4aEwXqoZOWRGkSmSgaG/FT0ii3DAgYjKTYZ
GotxqmcshpXOJXjTnXnBKsMo2xIQeiqkYyuDD8bqlp/6XkgL1ohCkRq8rjNt85xv
MN0yzspp9HpKl/EScwIDAQAB
-----END PUBLIC KEY-----
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANzl73qv6Lay41Bv
MFRXzq1PIFZEhOZ1vZnjoFpNY2VvePFCSHez3u0jhoTBeqhk5ZEaRKZKBob8VPSK
LcMCBiMpNhkai3GqZyyGlc4leNOdecEqwyjbEhB6KqRjK4MPxuqWn/peSAvWiEKR
GryuM23znG8w3TLOymn0ekqX8RJzAgMBAAECgYAClq83wNf5TB9d0e+/DUhev46h
dCwah0axhvlaFY4UojnImf4/aNwz6zaoV5wYXRZTnPsw960b59kXBIeEwYFQ4pdd
gBlRnwZ9Dv5i4an1pGvZ7MA9hlUlFpEtTJ95HL4b4WpKL+jpNTStT8dItf1FISVk
NDIwdSZFjCE0/VNAAQJBAPSeUsunMbRkz2oJRXBCkvRoxnb9gemgkC4yokCxd4So
iWBsqZDMQJ4x5RmdIbzwyFlyeBlmmDsb+P/mWsPPZ1MCQQDnLRPR860t+QCeET4G
Q9EzPDKAYjoV4wcPNrgzjXd4tiwJeh1nHhaDtYal9XEvREYhUtk6qmLMGuAnqOWm
t+RhAkEAoFtRh3OZH9qeJbLiNE9QKqysvcA987twCPjkaGhuIyagt/dDyUo8affn
ab0aKtPlYs2pcW1SCh2yQ37srUQ/RQJBAMEkvVGFmKQ3TRfDaiHL2WZIHh17c/JD
WuuQGTghMrcs5QAKAbTcw4zJRjU0KpuGHF3NHWdRYfgLYEpiZ3TyYSECQQDgMqF1
yP5L7fVGVn2tCY5aQ1nDybs0piQ6Op6GisIJ8xecPa/SSRFbdncJnwyWC0bvw4Uj
k40U/iT/Huk6faRK
-----END PRIVATE KEY-----
公钥
公钥指数及模数信息:
key长度:	1024
模数:	DCE5EF7AAFE8B6B2E3506F305457CEAD4F20564484E675BD99E3A05A4D63656F78F1424877B3DEED238684C17AA864E5911A44A64A0686FC54F48A2DC30206232936191A8B71AA672C8695CE2578D39D79C12AC328DB12107A2AA4632B830FC6EA969FFA5E480BD68842911ABCAE336DF39C6F30DD32CECA69F47A4A97F11273
指数:	65537 (0x10001)

    

有兴趣的朋友可以在  http://www.lab-z.com/openssl-for-windows/ 找到一个 Windows 版本的 Open(当然这样的操作很不安全,最好是自己下载源代码进行编译。信息安全技术和传统文化一样,总是能找到“喷点”,譬如,高考语文是大众唯一能够热烈讨论的科目。春晚上的春联一定会被人找到不合时宜之处。)

RSA 算法的缺陷主要是加密速度很慢(RSA的速度是对应同样安全级别的对称密码算法的1/1000左右【参考1】)。因此,通常是和对称加密算法结合起来使用。比如:使用 RSA 算法传递一个足够长的密码,然后用DES 之类的使用这个密码进行加密和解密。

有一段时间,出于军事原因,加密算法属于美国政府的出口管制项目,但是随着英特网的蓬勃发展,加密技术变得无处不在,很多之前的机密算法也逐渐变得公开。有兴趣的朋友可以在【参考4】读到关于这方面的历史。

另外,还有一个很有趣的事情。RSA 算法本身足够安全,但是 RSA 密码对的生成未必是安全的。“援引路透社报道根据斯诺登泄漏的文件称受国家标准委员会NIST批准,美国美国安全局(NSA)和加密公司RSA达成了价值超过$1000万的协议,要求在移动终端中广泛使用的加密技术中放置后门,能够让NSA通过随机数生成算法Bsafe的后门程序轻易破解各种加密数据。对此RSA否认了相关的内容,声 称自己的加密算法只使用了国家认证的协议。而NSA则拒绝发表评论。

  RSA Security是由RSA算法的发明者Ron Rivest, Adi Shamir和Len Adleman在1982年创立,随后在2006年以21亿美元的价格被EMC公司收购。其中该算法最为有名的一个缺陷就是DUAL_EC_DRBG,密码学家早在几年前就发现了这个问题。这个加密算法可以看作是随机数生成器,但是有些数字是固定的,密码学家能够将其作为万能钥匙通过一些内置的算法进行破解。

我们知道RSA算法本身没什么问题,因为只要你的密钥是真正随机产生的,猜对这个密钥就如同大海捞针一般难,现有计算机肯定无法在密码更换周期内攻破你的加密档案。但是,如果这个随机算法是假的呢?如果它仅仅是在一个很小的集合中产生的密钥呢?你的加密档案瞬间就被查看了,这就是NSA干的事情。”【参考5】

下次,我会介绍如何在 UEFI Shell 中实现一个简单的 RSA 加密和解密。

参考:

  1. “月黑雁飞高,单于夜遁逃” 其中的“单于”发音是Chán yú;我以为念成“dàn yú”,所以对方无法解密。
  2. https://baike.baidu.com/item/RSA 算法
  3. https://www.zhihu.com/question/25038691?sort=created RSA 算法的加密原理是什么?
  4. https://en.wikipedia.org/wiki/Export_of_cryptography_from_the_United_States
  5. https://www.williamlong.info/archives/3699.html路透社称NSA向RSA加密算法中放置后门

Step to UEFI (170)Application 中使用 DEBUG 宏

DEBUG 是我们在代码中常见的宏,本文介绍如何在编写的 UEFI Shell Application 中使用它。

首先编写一个测试的 Application,其中使用了2次 DEBUG宏:

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/DebugLib.h>
#include <Library/ShellCEntryLib.h>

/**
  UEFI application entry point which has an interface similar to a
  standard C main function.

  The ShellCEntryLib library instance wrappers the actual UEFI application
  entry point and calls this ShellAppMain function.

  @param  ImageHandle  The image handle of the UEFI Application.
  @param  SystemTable  A pointer to the EFI System Table.

  @retval  0               The application exited normally.
  @retval  Other           An error occurred.

**/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{

      DEBUG ((DEBUG_ERROR, "DEBUG_ERROR OUTPUT\n"));
      DEBUG ((DEBUG_INFO,  "DEBUG_INFO OUTPUT\n"));

  return EFI_SUCCESS;
}

具体的 DEBUG宏定义可以在 \MdePkg\Include\Library\DebugLib.h 看到:

/**  
  Macro that calls DebugPrint().

  If MDEPKG_NDEBUG is not defined and the DEBUG_PROPERTY_DEBUG_PRINT_ENABLED 
  bit of PcdDebugProperyMask is set, then this macro passes Expression to 
  DebugPrint().

  @param  Expression  Expression containing an error level, a format string, 
                      and a variable argument list based on the format string.
  

**/
#if !defined(MDEPKG_NDEBUG)      
  #define DEBUG(Expression)        \
    do {                           \
      if (DebugPrintEnabled ()) {  \
        _DEBUG (Expression);       \
      }                            \
    } while (FALSE)
#else
  #define DEBUG(Expression)
#endif

其中用到的 _DEBUG 在同一个文件中:

/**  
  Internal worker macro that calls DebugPrint().

  This macro calls DebugPrint() passing in the debug error level, a format 
  string, and a variable argument list.
  __VA_ARGS__ is not supported by EBC compiler, Microsoft Visual Studio .NET 2003
  and Microsoft Windows Server 2003 Driver Development Kit (Microsoft WINDDK) version 3790.1830.

  @param  Expression  Expression containing an error level, a format string, 
                      and a variable argument list based on the format string.

**/

#if !defined(MDE_CPU_EBC) && (!defined (_MSC_VER) || _MSC_VER > 1400)
  #define _DEBUG_PRINT(PrintLevel, ...)              \
    do {                                             \
      if (DebugPrintLevelEnabled (PrintLevel)) {     \
        DebugPrint (PrintLevel, ##__VA_ARGS__);      \
      }                                              \
    } while (FALSE)
  #define _DEBUG(Expression)   _DEBUG_PRINT Expression
#else
#define _DEBUG(Expression)   DebugPrint Expression
#endif

上述都只是头文件,并没有真正“干活”的代码。在编译过程中可以 Link到不同的 C文件来完成。

如果想要让 DEBUG 能够正常输出,需要修改  \AppPkg\AppPkg.dsc 中的下面2行:

1.设置  DEFINE DEBUG_ENABLE_OUTPUT      = TRUE       # Set to TRUE to enable debug output

        设置为 TRUE 之后,编译时,会将需要的DEBUG函数Link 到存在的 *.C 文件上,否则会Link 给一个没有内容的函数。这是一种非常灵活的方法,可以在冗余的DEBUG 版本和干净的 Release 版本之间切换。

设置之后会 Link 到  \MdePkg\Library\UefiDebugLibConOut\DebugLib.c 文件上:

/**  
  Returns TRUE if DEBUG() macros are enabled.

  This function returns TRUE if the DEBUG_PROPERTY_DEBUG_PRINT_ENABLED bit of 
  PcdDebugProperyMask is set.  Otherwise FALSE is returned.

  @retval  TRUE    The DEBUG_PROPERTY_DEBUG_PRINT_ENABLED bit of PcdDebugProperyMask is set.
  @retval  FALSE   The DEBUG_PROPERTY_DEBUG_PRINT_ENABLED bit of PcdDebugProperyMask is clear.

**/
BOOLEAN
EFIAPI
DebugPrintEnabled (
  VOID
  )
{
  return (BOOLEAN) ((PcdGet8(PcdDebugPropertyMask) & DEBUG_PROPERTY_DEBUG_PRINT_ENABLED) != 0);
}

2.  设定 DEFINE DEBUG_PROPERTY_MASK      = 2 意思是打开 DEBUG PRINT ENABLED 具体定义如下:

//
// Declare bits for PcdDebugPropertyMask
//
#define DEBUG_PROPERTY_DEBUG_ASSERT_ENABLED       0x01
#define DEBUG_PROPERTY_DEBUG_PRINT_ENABLED        0x02
#define DEBUG_PROPERTY_DEBUG_CODE_ENABLED         0x04
#define DEBUG_PROPERTY_CLEAR_MEMORY_ENABLED       0x08
#define DEBUG_PROPERTY_ASSERT_BREAKPOINT_ENABLED  0x10
#define DEBUG_PROPERTY_ASSERT_DEADLOOP_ENABLED    0x20

之后, Build 上面的 Application 即可得到 EFI  Application,并且这个和使用 –b 选择 RELEASE DEBUG Mode 无关。

运行结果:

完整代码下载:

UEFI SPEC

UEFI 相关的 Spec 可以在 https://uefi.org/specifications 下载到。但是国内访问并不稳定,因此在这里放置一份:

1.ACPI Specification
当前最新版本 ACPI Specification Version 6.3

2.UEFI Specification
当前最新版本 UEFI Specification Version 2.7 (Errata A)

3.UEFI Shell Specification
当前最新版本 UEFI Shell Specification Version 2.2

4.UEFI Platform Initialization Specification 如果你发现代码里面有看起来很正规的 Protocol 但是 UEFI Spec 中找不到,可以到这份Spec中看看
当前最新版本 UEFI Platform Initialization Specification Version 1.7

5.UEFI Platform Initialization Distribution Packaging Specification
当前最新版本 UEFI Platform Initialization Distribution Packaging Specification Version 1.1

Step to UEFI (169)BMP 放在 EFI 文件中(下)

这篇文章的上篇是2015年8月写的【参考1】,三年后终于填坑。时隔久远先介绍一下目标:很多时候我们的代码需要显示图片,但是我们不希望图片作为单独的文件放在外面,因此需要想办法“打包”到 EFI中。第一种方法就是用工具将图片转换为 C 头定义,然后在编译中直接内存调用,这就是上篇使用的方法。可以看到比较麻烦也不算正规。最近偶然看到了EFI_HII_PACKAGE_IMAGES 的定义,琢磨了一下可以通过这样的方法将图片打包成为 EFI文件中的一个 Section,然后再进行解析调用。需要注意的是之前版本的 UDK 中并不支持这种方法,本文实验都是在 UDK2018 中进行的【参考2】。

先介绍一下原理(网上无法找到EFI_HII_PACKAGE_IMAGES 的使用方法,下面的都是实验得出的,如果有问题欢迎指出。【参考3】提供了一个关于 HII 的介绍,建议阅读本文之前先浏览此文):

1. 在代码中设定一个 *.idx 文件,其中用下面这样的形式给出要打包的 Image
#image IMG_LOGO TestImage.bmp

2. 其中的IMG_LOGO 是一个编号,代码中并没有定义,但是在 Build 之后的目录中能找到, 这应该是对应工具在编译过程中自动生成的

#define IMG_LOGO 0x0001

3. *.c 代码中一定要有 IMAGE_TOKEN (IMG_LOGO) 这样的定义,即使放在注释中也无所谓,但是一定要有,否则无法生成 2 提到的定义,应该是有工具会做扫描

4. 有了 HII 的定义,在编译时会在生成的 EFI 中加入一个 名称为 HII的Section,和通常的 Windows PE 处理 Resource 文件的方法一样

5. 在 Build 目录的 OUTPUT 下面,有一个生成的 hithii.rc,可以用 VS 直接打开之,看到的内容和上面的是相同的

6. 使用 Hii Package List Protocol 可以取得 PackageListHeader

7. 取得了 PackageListHeader 之后整体格式如下

下面是手工分析的结果,涉及到的结构体可以在 UefiInternalFormRepresentation.h 中看到。

最终代码如下:
HIIImageTest.c

/** @file
  Logo DXE Driver, install Edkii Platform Logo protocol.

Copyright (c) 2016 - 2017, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/


#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/HiiDatabase.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/HiiImageEx.h>
#include <Protocol/PlatformLogo.h>
#include <Protocol/HiiPackageList.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/PrintLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseLib.h>

#include <Library/UefiApplicationEntryPoint.h>

#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID \
  { \
    0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a } \
  }
  
static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;

//DO NOT REMOVE IMAGE_TOKEN (IMG_LOGO)

/**
  Entrypoint of this module.

  This function is the entrypoint of this module. It installs the Edkii
  Platform Logo protocol.

  @param  ImageHandle       The firmware allocated handle for the EFI image.
  @param  SystemTable       A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
)
{
  EFI_STATUS                  Status;
  EFI_HII_PACKAGE_LIST_HEADER *PackageListHeader;
  EFI_HII_IMAGE_PACKAGE_HDR   *ImageHeader;
  EFI_HII_IMAGE_BLOCK         *ImageBlocktType;
  EFI_HII_IIBT_IMAGE_24BIT_BASE  *ImageData;
  EFI_GRAPHICS_OUTPUT_PROTOCOL   *GraphicsOutput;
  UINT16        i,j;
  UINT8*        RGB32;
  
        //Step1. Get Package List Header Address
        
        //
        // Retrieve HII package list from ImageHandle
        //
        Status = gBS->OpenProtocol (
                        ImageHandle,
                        &gEfiHiiPackageListProtocolGuid,
                        (VOID **) &PackageListHeader,
                        ImageHandle,
                        NULL,
                        EFI_OPEN_PROTOCOL_GET_PROTOCOL
                        );
                        
        if (EFI_ERROR (Status)) {
          Print(L"HII Image Package with logo not found in PE/COFF resource section\n");
          return Status;
        }
        
        Print(L"PackageList :\n   GUID=[%g] Length=[%X]\n",
                      PackageListHeader->PackageListGuid,
                      PackageListHeader->PackageLength);
     
        //Step2. Parser HII Image
        ImageHeader=(EFI_HII_IMAGE_PACKAGE_HDR*)(PackageListHeader+1);
        Print(L"Image Header:\n Length=[%d]  Type=[%d]\n",ImageHeader->Header.Length,ImageHeader->Header.Type);
        Print(L"ImageOffset %d  PalOffset %d\n",ImageHeader->ImageInfoOffset,ImageHeader->PaletteInfoOffset);
     
        ImageBlocktType=(EFI_HII_IMAGE_BLOCK *)(ImageHeader+1);
        Print(L"ImageBlockType %x\n",*ImageBlocktType);
     
        ImageData=(EFI_HII_IIBT_IMAGE_24BIT_BASE *)(ImageBlocktType+1);
        Print(L"ImageData:\n  Width=[%d]   Heigth=[%d]\n",ImageData->Width,ImageData->Height);

        Status = gBS->LocateProtocol(&GraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
        if (EFI_ERROR(Status)) {
                Print(L"Loading Graphics_Output_Protocol error!\n");
                return EFI_SUCCESS;
        }

        //Step3. Get BMP Image
        RGB32 = (UINT8*)AllocatePool(ImageData->Width *  ImageData->Height *4);
        for (j=0;j<ImageData->Height;j++) {
                for (i=0;i<ImageData->Width;i++) {
                        RGB32[(j*ImageData->Width+i)*4]  =  ImageData->Bitmap[(j*ImageData->Width+i)].b;//Blue   
                        RGB32[(j*ImageData->Width+i)*4+1]=  ImageData->Bitmap[(j*ImageData->Width+i)].g; //Green 
                        RGB32[(j*ImageData->Width+i)*4+2]=  ImageData->Bitmap[(j*ImageData->Width+i)].r; //Red  
                        RGB32[(j*ImageData->Width+i)*4+3]=  0;
                }
        }   
        
        //Step4. Show the Image
        GraphicsOutput->Blt(
                        GraphicsOutput, 
                        (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) RGB32,
                        EfiBltBufferToVideo,
                        0, 0, 
                        200, 0, 
                        ImageData->Width, ImageData->Height, 0); 
        
        FreePool(RGB32);
        
  return Status;
}

HIIImageTest.idf
#image IMG_LOGO TestImage.bmp

#image IMG_LOGO TestImage.bmp

HIIImageTest.inf

## @file
#  The default logo bitmap picture shown on setup screen.
#
#  Copyright (c) 2016 - 2017, Intel Corporation. All rights reserved.<BR>
#
#  This program and the accompanying materials
#  are licensed and made available under the terms and conditions of the BSD License
#  which accompanies this distribution. The full text of the license may be found at
#  http://opensource.org/licenses/bsd-license.php
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
#
##

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = hit
  MODULE_UNI_FILE                = LogoDxe.uni
  FILE_GUID                      = F74D20EE-37E7-48FC-97F7-9B1047749C69
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain
  
#
#  This flag specifies whether HII resource section is generated into PE image.
#
  UEFI_HII_RESOURCE_SECTION      = TRUE

#
# The following information is for reference only and not required by the build tools.
#
#  VALID_ARCHITECTURES           = IA32 X64
#

[Sources]
  TestImage.bmp
  HIIImageTest.c
  HIIImageTest.idf

[Packages]
  MdeModulePkg/MdeModulePkg.dec
  MdePkg/MdePkg.dec
  StdLib/StdLib.dec   
  ShellPkg/ShellPkg.dec 
  
[LibraryClasses]
  DebugLib
  UefiApplicationEntryPoint
  PrintLib    
  UefiLib
  
[Protocols]
  gEfiHiiPackageListProtocolGuid     ## PRODUCES CONSUMES

运行结果(NT32 环境下运行结果,实体机上相同):

总结:从实验可以看到,需要处理的图片是作为PE 的 Resource 存在 EFI 中的,用这样的方法应该也会有机会将一些其他二进制文件打包到EFI 文件中。
完整的代码下载

参考:

  1. http://www.lab-z.com/bmpinefi/ Step to UEFI (59) —– BMP 放在EFI文件中(上)
  2. https://github.com/tianocore/edk2/commit/333ba578fef4dff8921051410c5b56f63e7eeadb 提到这个事情。为了支持这个功能需要修改编译工具,应该从 UDK2017 就已经支持:

BaseTools: support generating image package from BMP/JPEG/PNG files

BaseTools add support to generating image package from BMP/JPEG/PNG

files.

1) New file type *.idf Image definition file to describe HII image

resource. It is the ASCII text file, and includes one or more “#image

IMAGE_ID [TRANSPARENT] ImageFileName”.

2) New IMAGE_TOKEN macro is used to refer to IMAGE_ID.

3) New AutoGen header file $(MODULE_NAME)ImgDefs.h to include the

generated ImageId definition.

4) New $(MODULE_NAME)Idf.hpk or $(MODULE_NAME)Images are generated

as the output binary HII image package.

Cc: Liming Gao <liming.gao@intel.com>

Contributed-under: TianoCore Contribution Agreement 1.0

Signed-off-by: Yonghong Zhu <yonghong.zhu@intel.com>

Reviewed-by: Liming Gao <liming.gao@intel.com>

3. https://blog.csdn.net/jiangwei0512/article/details/80386945 BIOS/UEFI基础——UEFI用户交互界面使用说明之C代码实现(HII Package)

面包板的 328P Arduino

用面包板来玩 DIP 封装的 328P 是绝对没问题的。需要注意的有如下几点:

1.如果出现可以刷 BootLoader 但是无法正常工作,请检查晶振是否起振(最好使用示波器);

2.如果 5V 工作正常,但是 3.3V 供电后看起来不工作(比如无法控制 Pin13 上的LED,或者串口无输出)。那么请检查 VCC 是否和 AVCC 连接起来。我不知道为什么这两个Pin 不连接的时候,5V 供电能够正常工作但是 3.3V 供电后就不工作。

Arduino 制作 HDMI Audio 测试工具

最近有客户报告:当插入 HDMI 从外接的显示器显示时,有一定概率出现显示正常但是 HDMI Audio 失效的情况。这个问题的概率不高,但是对客户影响严重。为了复制现象,需要不断插拔HDMI来验证,经常是插拔50次之后抓到一次现象,随之而来的是再来50次,更可怕的是据说某些修改之后会出现插拔100次才会出现一次现象。这对于我这样只愿意动脑不愿动手的人来说是一个极大的考验。

这种情况下,必须考虑用机器来实现自动化的测试。思路是淘宝上买一个带有遥控器的HDMI切换器,通过 Arduino 模拟遥控的操作来实现切换到 HDMI 上然后进行检测。于是,选择了下面这货14元+8元的运费。这个切换器有三个输入口,可以对应三台HDMI输出设备;有一个输出口,可以连接一台显示器

接下来要解决的问题如下:
1. 如何进行遥控?实现遥控之后如何保证切换成功(红外线遥控容易受到光线的影响)。
2. 如何检测当前是Audio 是否正常。
经过研究,第一个问题可以使用下面这个模块来解决:
红外解码模块 编码模块 红外无线通信 接收发射串口通信模块

因为这是发射和接收一体的模块,刚好可以用来解码HDMI切换器发射器的数据,解码之后发射同样的内容就能实现遥控的操作。遥控板上有3个按键,分别对应HDMI 的三个端口。经过实验遥控板按键发送数据如下:
HDMI IN1: 0xF1 0x80 0x7F 0x02 0xA1
HDMI IN2: 0xA1 0xF1 0x80 0x7F 0x04
HDMI IN3: 0xA1 0xF1 0x80 0x7F 0x06
接下来还有一个问题,如何保证切换正常。比如,当前是 HDMI1 输出,我切花发送切换HDMI2命令之后需要确认切换是否成功(红外遥控受到环境影响较大)。经过研究,可以使用切换器上的LED来判断,每一个输入端口都有一个对应的LED用来表示当前选中的输入端口。只需要读取LED电平即可得知。此外,我们再使用额外的LED来标记当前的输入 HDMI Port。
最终,硬件连接如下:


完整代码:

const int LED1=14;
const int LED2=16;
const int LED3=10;

void setup()
{
  pinMode(LED1,OUTPUT);
  pinMode(LED2,OUTPUT);
  pinMode(LED3,OUTPUT);
  digitalWrite(LED1,LOW);
  digitalWrite(LED2,LOW);
  digitalWrite(LED3,LOW);

  pinMode(A1,INPUT_PULLUP);
  pinMode(A2,INPUT_PULLUP);
  pinMode(A3,INPUT_PULLUP);
    
    Serial.begin(9600);
    Serial1.begin(9600);    
    

}

void loop()
{
  char  c=' ';
  int   retry;
    while (Serial.available() > 0)  
    {
        c=Serial.read();
        if (c=='1') { //Command for Switch to HDMI1
            retry=3;
            while (retry>0) {
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x02);
              Serial1.write(0xA1);
              delay(200);
              if (analogRead(A1)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go1");
            }
        }
        if (c=='2') {//Command for Switch to HDMI2
            retry=3;
            while (retry>0) {
              Serial1.write(0xA1);
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x04);
              delay(200);
              if (analogRead(A2)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go2");
            } 
        }
        if (c=='3') {//Command for Switch to HDMI3
            retry=3;
            while (retry>0) {
              Serial1.write(0xA1);
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x06);
              delay(200);
              if (analogRead(A3)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go3");
              }
        } //if (c=='3')
    }
    
   if (analogRead(A1)<20) {digitalWrite(LED1,HIGH);}
   else {digitalWrite(LED1,LOW);}
   if (analogRead(A2)<20) {digitalWrite(LED2,HIGH);}
   else {digitalWrite(LED2,LOW);}
   if (analogRead(A3)<20) {digitalWrite(LED3,HIGH);}
   else {digitalWrite(LED3,LOW);}        
}

 

为了配合这个设备,我们需要编写上位机的代码, 上位机是运行在被测试机器上。主要的逻辑就是:发送切换命令切换到其他HDMI Port,之后再发送命令切换回来,然后检查音频是否正常,如果不正常就停止。如果正常就继续运行。软件界面如下:

代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;

using System.Runtime.InteropServices;

namespace WindowsFormsApplication8
{
    public partial class Form1 : Form
    {   //当前切换到 HDMI[x]
        int current=1;
        //切换次数
        int loops = 0;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //如果当前测试还没有开始,那么开始测试,否则停止
            if (button1.Text == "Start") { button1.Text = "Stop"; }
            else { button1.Text = "Start"; timer1.Enabled = false;  return; }

            //检查选中的 HDMI 数量
            int counter = 0;
            for (int i= 0; i < 3; i++) {
                if (checkedListBox1.GetItemChecked(i)) { counter++; }
            }
            //如果只有1个HDMI 被选中,提示出错
            if (counter < 2) { System.Windows.Forms.MessageBox.Show("Please choose at least 2 HDMI"); return; }
            if (serialPort1.IsOpen) { serialPort1.Close(); }
            //打开选中的 COMx
            serialPort1.PortName = comboBox1.SelectedItem.ToString();
            serialPort1.Open();
            textBox1.AppendText("Open " + comboBox1.SelectedItem.ToString()+"\n");
            //1秒之内开始测试
            timer1.Interval = 1000;
            timer1.Enabled = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //设置可以选择的串口号
            comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
            comboBox1.SelectedIndex = 0;
            for (int i = 0; i < checkedListBox1.Items.Count; i++) {
                checkedListBox1.SetItemChecked(i,true);
            }

            //输出当前的串口设备名称
            ManagementObjectSearcher searcher =  new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PnPEntity");
            foreach (ManagementObject queryObj in searcher.Get())
            {
                if (queryObj["Caption"] != null)
                    if (queryObj["Caption"].ToString().Contains("(COM"))
                    {
                        //Console.WriteLine(queryObj["Caption"]);
                        textBox1.AppendText(queryObj["Caption"].ToString()+"\n");
                    }
            }
            searcher.Dispose();

        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serialPort1.IsOpen) {
                textBox1.AppendText("close " + comboBox1.SelectedItem.ToString());
                serialPort1.Close();
            };
        }


        // native calls
        [DllImport("winmm.dll")]
        public static extern int waveOutGetNumDevs();

        private void timer1_Tick(object sender, EventArgs e)
        {
            //如果当前是 HDMI2
            if (current == 2) {
                //检查设备是否有 Audio
                int inum = waveOutGetNumDevs();
                if (inum == int.Parse(maskedTextBox1.Text))  //发现错误
                {
                    textBox1.AppendText("Audio failed");
                    timer1.Enabled = false;
                    return;
                }
            }
            //20秒后触发下一次切换
            if (timer1.Interval == 1000) { timer1.Interval = 20000; }
            //尝试下一个HDMI
            current = (current + 1) % 3+1;
            //检查下一个是否被选中
            while (checkedListBox1.GetItemChecked(current-1)==false) {
                current = (current + 1) % 3+1;
            }
            if (current == 2) { loops++; }
            //切换到下一个可用的HDMI设备
            textBox1.AppendText("Switch to " + current.ToString()+"\n");
            textBox1.AppendText("Loops " + loops.ToString() + "\n");
            serialPort1.Write(current.ToString());
        }

        private void label1_Click(object sender, EventArgs e)
        {

        }
    }


}

 

编译 Windows 下的 EDAX

EDAX 是一款很厉害的黑白棋引擎,项目地址是https://github.com/abulmo/edax-reversi
除了引擎,它提供了一个文本的简单界面,用户能够方便的进行测试。本文介绍一下如何实现使用 VS2015编译出一个能够工作的EXE文件。

1. 在https://github.com/abulmo/edax-reversi/releases 页面有如下下载链接。Edax-linux.7z 是 Linux 版本的可执行程序;edax-ms-windows 是 Windows版本的可行性程序;eval 是开局库,运行的时候必须放在可执行文件同一个目录中;最后是不同压缩格式的两个Source Code。

对我们来说,需要下载 Source Code.zip

2. 对于下载的代码,options.c 文件需要进行修改,其中定义了const char *(boolean[2]) = {“false”, “true”}; 这样的变量,变量名称 Boolean 不妥,我的解决方法是修改为const char *(booleanx[2]) = {“false”, “true”}; 然后将这个文件中的所有 boolean 都修改为 booleanx。

3. 编译命令(我没有搞清楚如何使用 nmake来编译这个项目)
cl /I”..\include” /O2 /GL /fp:fast /favor:INTEL64 /arch:AVX /D “NDEBUG” /D “inline=__inline” /D “__func__=__FUNCTION__” /D “USE_MSVC_X64” /D “POPCOUNT” /MT all.c ws2_32.lib /FewEdax-x64.exe /link /ltcg:pgi /machine:x64 /VERSION:4.4

4. 下载 eval.dat,解压到 data 目录下就可以运行测试了

一站式获得系统信息的工具 Intel SSU

很多时候,为了分析研究现象,我们需要收集一些信息,比如:当前Windows版本,BIOS 版本或者 ME 版本等等。 Intel SSU 就是为此设计的。这个工具介绍如下:

Intel® System Support Utility. Intel® System Support Utility (Intel® SSU) is a standalone utility that performs a detailed scan and report of your computer system information and devices. Intel SSU produces an output file that can be saved, viewed, and shared by the user.

可以在下面的链接下载到:

https://downloadcenter.intel.com/download/25293/Intel-System-Support-Utility-for-Windows-

这个软件无需安装,下载后执行即可:

点击 Scan 后可以得到下面的结果


可以切换到更详细的信息栏

有兴趣的朋友可以试试。