Step to UEFI (175)Shell 下读取 VBT

最近看到一篇文章“Intel ACPI IGD OpRegion Spec, and IntelSiliconPkg added to Tianocore”【参考1】,介绍的是Intel Jiewen Yao在开源项目 Tianocore 中加入了关于 IGD OpRegion相关内容。与这个文件相关有一份名为“Intel Integrated Graphics Device OpRegion Specification Driver Programmer’s Reference Manual” 的 datasheet【参考2】。简单的说这是介绍 Intel IGD 显卡控制接口的资料。上面提到的下面这个表格引起了我的兴趣:

就是说,如果我们能在内存中找到OpRegion 那么是有机会找到 VBT 的。如果能找到 VBT 那么就有机会 Dump 出来。更出人意料的是,这份Datasheet 直接给出了获得 OpRegion 的方法:

结合【参考1】的Code, IGD  OpRegion 结构如下(【参考2】也提到了下面的结构体,但是VBT 的 Offset 是 0x500和实际对不上,有可能是因为 Datasheet 太老):

///
	/// IGD OpRegion Structure
	///
	typedef struct {
	  IGD_OPREGION_HEADER Header; ///< OpRegion header (Offset 0x0, Size 0x100)
	  IGD_OPREGION_MBOX1  MBox1;  ///< Mailbox 1: Public ACPI Methods (Offset 0x100, Size 0x100)
	  IGD_OPREGION_MBOX2  MBox2;  ///< Mailbox 2: Software SCI Interface (Offset 0x200, Size 0x100)
	  IGD_OPREGION_MBOX3  MBox3;  ///< Mailbox 3: BIOS to Driver Notification (Offset 0x300, Size 0x100)
	  IGD_OPREGION_MBOX4  MBox4;  ///< Mailbox 4: Video BIOS Table (VBT) (Offset 0x400, Size 0x1800)
	  IGD_OPREGION_MBOX5  MBox5;  ///< Mailbox 5: BIOS to Driver Notification Extension (Offset 0x1C00, Size 0x400)
	} IGD_OPREGION_STRUCTURE;

接下来在 Kabylake-R 的板子上验证一下:

  1. 取得OpRegion,地址为 0x2FEDC018

2.查看内存

3.到 +0x400的地方查看,可以看到 $VBT 的 Sign

该处内容和我代码中的 VBT 一致。接下来的问题是,如何得知VBT 的大小。在UDK2018 的Source Code 中有下面的文件 Vlv2TbltDevicePkg\VlvPlatformInitDxe\IgdOpRegion.h,其中定义如下:

#pragma pack (1)
typedef struct {
  UINT8   HeaderSignature[20];
  UINT16  HeaderVersion;
  UINT16  HeaderSize;
  UINT16  HeaderVbtSize;
  UINT8   HeaderVbtCheckSum;
  UINT8   HeaderReserved;
  UINT32  HeaderOffsetVbtDataBlock;
  UINT32  HeaderOffsetAim1;
  UINT32  HeaderOffsetAim2;
  UINT32  HeaderOffsetAim3;
  UINT32  HeaderOffsetAim4;
  UINT8   DataHeaderSignature[16];
  UINT16  DataHeaderVersion;
  UINT16  DataHeaderSize;
  UINT16  DataHeaderDataBlockSize;
  UINT8   CoreBlockId;
  UINT16  CoreBlockSize;
  UINT16  CoreBlockBiosSize;
  UINT8   CoreBlockBiosType;
  UINT8   CoreBlockReleaseStatus;
  UINT8   CoreBlockHWSupported;
  UINT8   CoreBlockIntegratedHW;
  UINT8   CoreBlockBiosBuild[4];
  UINT8   CoreBlockBiosSignOn[155];
} VBIOS_VBT_STRUCTURE;
#pragma pack ()

于是我们也可以获得 VBT 的大小。完整的代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/SimpleFileSystem.h>
#include  <Library/UefiBootServicesTableLib.h> //global gST gBS gImageHandle
#include  <Library/ShellLib.h>

#include  "IgdOpRegion.h"

//Copied from UDK2018\Vlv2TbltDevicePkg\VlvPlatformInitDxe\IgdOpRegion.h
#pragma pack (1)
typedef struct {
  UINT8   HeaderSignature[20];
  UINT16  HeaderVersion;
  UINT16  HeaderSize;
  UINT16  HeaderVbtSize;
  UINT8   HeaderVbtCheckSum;
  UINT8   HeaderReserved;
  UINT32  HeaderOffsetVbtDataBlock;
  UINT32  HeaderOffsetAim1;
  UINT32  HeaderOffsetAim2;
  UINT32  HeaderOffsetAim3;
  UINT32  HeaderOffsetAim4;
  UINT8   DataHeaderSignature[16];
  UINT16  DataHeaderVersion;
  UINT16  DataHeaderSize;
  UINT16  DataHeaderDataBlockSize;
  UINT8   CoreBlockId;
  UINT16  CoreBlockSize;
  UINT16  CoreBlockBiosSize;
  UINT8   CoreBlockBiosType;
  UINT8   CoreBlockReleaseStatus;
  UINT8   CoreBlockHWSupported;
  UINT8   CoreBlockIntegratedHW;
  UINT8   CoreBlockBiosBuild[4];
  UINT8   CoreBlockBiosSignOn[155];
} VBIOS_VBT_STRUCTURE;
#pragma pack ()

#define MmPciAddress(Bus, Device, Function, Register) \
  ((UINTN) 0xE0000000 + \
   (UINTN) (Bus << 20) + \
   (UINTN) (Device << 15) + \
   (UINTN) (Function << 12) + \
   (UINTN) (Register) \
  )
  
EFI_GUID        gEfiSimpleFileSystemProtocolGuid ={ 0x964E5B22, 0x6459, 0x11D2, 
                        { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
                        
//                        
//Save memory address to a file                        
//
EFI_STATUS 
SaveToFile(
        IN UINT8 *FileData, 
        IN UINTN FileDataLength)
{
    EFI_STATUS          Status;
    EFI_FILE_PROTOCOL   *FileHandle;
    UINTN               BufferSize;
    EFI_FILE_PROTOCOL   *Root;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;

    Status = gBS->LocateProtocol(
                &gEfiSimpleFileSystemProtocolGuid, 
                NULL,
                (VOID **)&SimpleFileSystem);
                
    if (EFI_ERROR(Status)) {
            Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
            return Status;
    }

    Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &Root);
    if (EFI_ERROR(Status)) {
        Print(L"OpenVolume error \r\n");
        return Status;
    }
    Status = Root->Open(
                Root, 
                &FileHandle, 
                L"dumpvbt.bin",
                EFI_FILE_MODE_READ |
                EFI_FILE_MODE_WRITE | 
                EFI_FILE_MODE_CREATE, 
                0);
    if (EFI_ERROR(Status)){
        Print(L"Error Open NULL  [%r]\n",Status);
        return Status;
    }
    
    BufferSize = FileDataLength;
    Status = FileHandle->Write(FileHandle, &BufferSize, FileData);
    if (EFI_ERROR(Status)){
        Print(L"Error write [%r]\n",Status);
        return Status;
    }
    else Print(L"VBT has been saved to 'dumpvbt.bin' \n");
    
    FileHandle->Close(FileHandle);
    
    return Status;
}

UINT32
EFIAPI
MmioRead32 (
  IN      UINTN                     Address
  )
{
  UINT32                            Value;

  Value = *(volatile UINT32*)Address;

  return Value;
}


/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        IGD_OPREGION_STRUCTURE  *IGDRegion;
        VBIOS_VBT_STRUCTURE     *VBT;
        
        // If can't find IGD, print a error message
        if ((UINT16)(MmPciAddress(0,2,0,0)==0xFFFF)) {
                Print(L"No IGF found! \n");
                return 0;
        }
        
        // Get IGD OPRegion
        IGDRegion = (IGD_OPREGION_STRUCTURE*) MmioRead32((UINTN)(MmPciAddress(0,2,0,0xFC)));
        Print(L"OpRegion Address [0x%X] \n",IGDRegion);
        
        //Get VBT address
        VBT=(VBIOS_VBT_STRUCTURE *) &IGDRegion->MBox4;
        
        Print(L"     VBT Address [0x%X] \n",VBT);
        Print(L"     VBT Size    [0x%X] \n",VBT->HeaderVbtSize);
        
        if ((VBT->HeaderVbtSize>8*1024)||(VBT->HeaderVbtSize==0)) 
                {
                        Print(L"VBT Data too big, ERROR! \n");
                }
        else 
          SaveToFile((UINT8*)VBT,VBT->HeaderVbtSize);
        
        return(0);
}

HDK  KBL-R  上的运行结果如下:

比较也会发现 dump VBT 和压入的相同。

完整代码下载:

参考:

1. https://firmwaresecurity.com/2016/06/01/intel-acpi-igd-opregion-spec-and-intelsiliconpkg-added-to-tianocore/

2. https://01.org/sites/default/files/documentation/acpi_igd_opregion_spec_0.pdf

UEFI TIPS:edk2-stable201903

Krishna 推荐EDK2中集成的  ACPI Command,研究过程中我发现今年3月有一份新Release 的 UDK 版本,可以在https://github.com/tianocore/edk2/releases/tag/edk2-stable201903 看到。从我的感觉上这个版本比前面 201811 的要稳定一些,推荐有更新需求的朋友们试试。

这个版本更新如下:

Release Date 2019-03-08 (UTC – 8 12PM)

New Features

其中引人注意的是 “Upgrade OpenSSL to 1.1.0j”,在编译过程中我发现如果没有 OpenSSL NT32Pkg 也是无法正常编译的。这里放置一个补充 OpenSSL 1.1.0J 能够正常编译的 Package,有需要的朋友可以在这里下载:

链接: https://pan.baidu.com/s/12pXFsgaskEIt1Y6dfuHtGg 提取码: ymra

FireBeetle打造腕表式心率计

心率是人类重要的生理指标,中国古人更是相信能够凭借脉搏诊断身体情况。

之前介绍过心率带和心率带接收模块的使用【参考1】。这次使用 DFRobot 的FireBeelte 和 12864 小屏幕制作一个腕表式心率计。

主控板 ESP32
配套 12864 显示板

从前面的实验可以得知,心率带接收模块使用串口输出。FireBeelte 核心是 ESP32 ,支持3个硬件串口【参考2】。其中0号是给烧写程序使用了(其实也可以用,只是为了调试方便而不选择使用);1号串口在 IO9/10和 12864 控制Pin冲突;所以只能使用2号串口,位于IO16/17。对我们来说只需要接收即可。

供电部分这次直接使用USB提供5V, 使用板载的 3.3V接口为心率接收模块供电。FireBeelte 设计上带有电池插口(3.7V),这次设计为了简单起见并未使用。

12864 屏幕自带了字库,因此可以直接调用库来完成汉字的现实,对于汉字显示需求来说方便很多。通过函数OLED.disStr(x, y, “要显示的汉字”) 即可直接显示。

代码很简单,就是从一个串口输入,找到心率数据,显示在 12864屏幕即可。

完整代码如下:

#include "DFRobot_OLED12864.h"

// 使用第三组串口
HardwareSerial Serial2(2);

// 使用12864 LCD
const uint8_t I2C_addr = 0x3c;
const uint8_t pin_SPI_cs = D5;

DFRobot_OLED12864 OLED(I2C_addr, pin_SPI_cs);

void setup(void)
{
  // 串口0 用于下载代码 
  Serial.begin(115200);
  // 串口2 用于接收心率带接收器发过来的数据
  Serial2.begin(9600);  
  
  // 初始化 12864
  OLED.init();
  OLED.flipScreenVertically();
}

  char Recv[40];
  char BPM[40]="----BPM";
  int  count=0;
  int  idx;
void loop(void)
{
  // 一直在串口2 接收数据 
  while (Serial2.available())
  {
    Recv[count]=Serial2.read();
    count++;
  }
  
  // 心率带数据以  0xDD 起始,例如: DD 22 05 28 4D 
  if ((Recv[0]==0xDD)&&(count==5)) {
      // 将心率数据格式化为字符串  
      for (int i=0;i<sizeof(BPM);i++) BPM[i]=' ';
      sprintf(BPM, "%d BPM   ", Recv[4]);
  }
  
  // 接收到足够的数据的话,将数据送到串口0 便于Debug
  if (count>=5) {
    for (int i=0;i<count;i++)
      Serial.write(Recv[i]);
    //清空接收 Buffer  
    count=0;
  } 
   
   for (idx=0;idx<20;idx++) {
     OLED.clear();
     // 将心率信息显示在 12864 上 
     OLED.disStr(0, 0, "当前心率");
     OLED.disStr(40, 30, BPM);  
     
     // 为了便于查看除了心率数据,还实现一个圆形的动态显示   
     for (int16_t i=0; i<abs(10-idx); i++) {
       OLED.drawCircle(116, 52, i);
     }  
    
     //显示内容到 12864 屏幕上
     OLED.display();  
     delay(30);
   }
   
}    

成品照片:

工作的视频可以在知乎专栏看到:

https://zhuanlan.zhihu.com/p/58751665

参考:

1. Arduino获得心率带数据

https://www.arduino.cn/forum.php?mod=viewthread&tid=84101&fromuid=36850

2. FireBeelte 多串口通讯的实现

https://www.arduino.cn/forum.php?mod=viewthread&tid=84390&fromuid=36850

Step to UEFI (174)UEFI Shell 下的贪吃蛇游戏

最近在  https://github.com/AlexClazrey/uefi-snake-game 发现一个UEFI 下的贪吃蛇游戏。下载之后在 UDk2018 下测试编译,修改Warning 后可以正常编译运行。有兴趣的朋友可以下载研究一下。

工作的图片:

编译后的 X64 EFI,可以在 NT32 模拟环境中运行:

原始代码:

修改后的代码:

Step to UEFI (173)UEFI Shell 下的 ACPI Dump工具

之前推荐过一个 Shell 下的 ACPI Table Dump工具【参考1】,没有 Source Code。从原理上来说没什么难度,只是比较麻烦一些而已。最近在https://github.com/andreiw 上看到他写的一些 UEFI Shell 工具组感觉很有意思,编译试验后逐次介绍给大家。这次介绍的是他写的 ACPI Dump 工具,在 【参考2】可以看到。

作者(Andrei Warkentin)对于这个工具的介绍如下:

# AcpiDump

AcpiDump will dump all of the ACPI tables to the same volume

the tool is located on. This is sometimes useful, but keep

in mind that some UEFI implementations can patch ACPI tables

on the way out during ExitBootServices, meaning you might

not get the same (correct) data if you dumped tables from

the OS proper.

An optional parameter specifies the path, relative to the

volume root, where to place the tables. E.g.:

    fs16:> AcpiDump.efi

    fs16:> AcpiDump.efi MyFunkySystem

The files produced can be loaded by [AcpiLoader](../AcpiLoader).

经过一些修改,他的工具可以独立在 UDK AppPkg 下编译通过,主程序如下:

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include "UtilsLib.h"

#include <IndustryStandard/Acpi.h>
#include <Protocol/LoadedImage.h>
#include <Library/PrintLib.h>
#include <Guid/Acpi.h>

#include <Library/ShellCEntryLib.h>

static RANGE_CHECK_CONTEXT RangeCheck;

static
EFI_STATUS
TableSave (
           IN EFI_HANDLE Handle,
           IN CHAR16 *VolSubDir,
           IN EFI_ACPI_DESCRIPTION_HEADER *Table
           )
{
  EFI_STATUS Status;
  static unsigned Index = 0;
  CHAR16 Path[3 + 4 + 1 + 3 + 1];

  if (Table == NULL) {
    Print(L"<skipping empty SDT entry\n");
    return EFI_NOT_FOUND;
  }

  Status = RangeIsMapped(&RangeCheck, (UINTN) Table, sizeof(EFI_ACPI_DESCRIPTION_HEADER));
  if (Status != EFI_SUCCESS) {
    Print(L"<could not validate mapping of table header: %r>\n", Status);
    return Status;
  }

  Print(L"Table %.4a @ %p (0x%x bytes)\n", &Table->Signature, Table, Table->Length);

  Status = RangeIsMapped(&RangeCheck, (UINTN) Table, Table->Length);
  if (Status != EFI_SUCCESS) {
    Print(L"<could not validate mapping of full table: %r>\n", Status);
    return Status;
  }

  Index = Index % 100;
  Path[0] = (CHAR16) ('0' + Index / 10);
  Path[1] = (CHAR16) ('0' + Index % 10);
  Path[2] = '-';
  Path[3] = ((CHAR8 *) &Table->Signature)[0];
  Path[4] = ((CHAR8 *) &Table->Signature)[1];
  Path[5] = ((CHAR8 *) &Table->Signature)[2];
  Path[6] = ((CHAR8 *) &Table->Signature)[3];
  Path[7] = L'.';
  Path[8] = L'a';
  Path[9] = L'm';
  Path[10] = L'l';
  Path[11] = L'\0';
  Index++;

  Status = FileSystemSave(Handle, VolSubDir, Path, Table, Table->Length);
  if (Status != EFI_SUCCESS) {
    //
    // FileSystemSave already does sufficient logging. We only
    // return failure if there was something wrong with the
    // table itself.
    //
    return EFI_SUCCESS;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
UefiMain (
          IN EFI_HANDLE ImageHandle,
          IN EFI_SYSTEM_TABLE *SystemTable
          )
{
  UINTN i;
  UINTN Argc;
  CHAR16 **Argv;
  EFI_STATUS Status;
  UINTN SdtEntrySize;
  EFI_PHYSICAL_ADDRESS SdtTable;
  EFI_PHYSICAL_ADDRESS SdtTableEnd;
  EFI_ACPI_DESCRIPTION_HEADER *Rsdt;
  EFI_ACPI_DESCRIPTION_HEADER *Xsdt;
  EFI_LOADED_IMAGE_PROTOCOL *ImageProtocol;
  EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp;
  CHAR16 *VolSubDir;

  EFI_GUID AcpiGuids[2] = {
    EFI_ACPI_20_TABLE_GUID,
    ACPI_10_TABLE_GUID
  };

  VolSubDir = L".";
  Status = GetShellArgcArgv(ImageHandle, &Argc, &Argv);
  if (Status == EFI_SUCCESS && Argc > 1) {
    VolSubDir = Argv[1];
  }

  Status = InitRangeCheckContext(TRUE, TRUE, &RangeCheck);
  if (EFI_ERROR(Status)) {
    Print(L"Couldn't initialize range checking: %r\n", Status);
    return Status;
  }

  Print(L"Dumping tables to '\\%s'\n", VolSubDir);

  Status = gBS->HandleProtocol (ImageHandle, &gEfiLoadedImageProtocolGuid,
                                (void **) &ImageProtocol);
  if (Status != EFI_SUCCESS) {
    Print(L"Could not get loaded image device handle: %r\n", Status);
    goto done;
  }

  for (i = 0; i < 2; i++) {
    Rsdp = GetTable(&AcpiGuids[i]);
    if (Rsdp != NULL) {
      break;
    }
  }

  if (Rsdp == NULL) {
    Print(L"No ACPI support found\n");
    goto done;
  }

  if (Rsdp->Revision >= EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION) {
    Xsdt = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Rsdp->XsdtAddress;
  } else {
    Xsdt = NULL;
  }

  Status = EFI_NOT_FOUND;
  Rsdt = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Rsdp->RsdtAddress;
  if (Xsdt != NULL &&
      (Status = RangeIsMapped(&RangeCheck, (UINTN) Xsdt,
                              sizeof(EFI_ACPI_DESCRIPTION_HEADER)))
      == EFI_SUCCESS) {
    SdtTable = (UINTN) Xsdt;
    SdtTableEnd = SdtTable + Xsdt->Length;
    SdtEntrySize = sizeof(UINT64);
  } else if (Rsdt != NULL &&
             (Status = RangeIsMapped(&RangeCheck, (UINTN) Rsdt,
                                     sizeof(EFI_ACPI_DESCRIPTION_HEADER)))
             == EFI_SUCCESS) {
    SdtTable = (UINTN) Rsdt;
    SdtTableEnd = SdtTable + Rsdt->Length;
    SdtEntrySize = sizeof(UINT32);
  } else {
    Print(L"No valid RSDT/XSDT: %r\n", Status);
    goto done;
  }

  Status = RangeIsMapped(&RangeCheck, SdtTable, SdtTableEnd - SdtTable);
  if (Status != EFI_SUCCESS) {
    Print(L"Could not validate RSDT/XSDT mapping: %r\n", Status);
    goto done;
  }

  for (SdtTable += sizeof(EFI_ACPI_DESCRIPTION_HEADER);
       SdtTable < SdtTableEnd;
       SdtTable += SdtEntrySize) {
    EFI_ACPI_DESCRIPTION_HEADER *TableHeader;

    if (SdtEntrySize == sizeof(UINT32)) {
      TableHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) *(UINT32 *) SdtTable;
    } else {
      TableHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) *(UINT64 *) SdtTable;
    }

    Status = TableSave(ImageProtocol->DeviceHandle, VolSubDir, TableHeader);

    if (Status == EFI_SUCCESS &&
        TableHeader->Signature == EFI_ACPI_5_1_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE) {
      EFI_ACPI_DESCRIPTION_HEADER *DsdtHeader;
      EFI_ACPI_DESCRIPTION_HEADER *FacsHeader;

      EFI_ACPI_5_1_FIXED_ACPI_DESCRIPTION_TABLE *Fadt = (void *) TableHeader;
      if (TableHeader->Revision >= 3 && Fadt->XDsdt != 0) {
        DsdtHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Fadt->XDsdt;
      } else {
        DsdtHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Fadt->Dsdt;
      }

      if (TableHeader->Revision >= 3 && Fadt->XFirmwareCtrl != 0) {
        FacsHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Fadt->XFirmwareCtrl;
      } else {
        FacsHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Fadt->FirmwareCtrl;
      }

      if (DsdtHeader != NULL) {
        TableSave(ImageProtocol->DeviceHandle, VolSubDir, DsdtHeader);
      } else {
        Print(L"No DSDT\n");
      }

      if (FacsHeader != NULL) {
        TableSave(ImageProtocol->DeviceHandle, VolSubDir, FacsHeader);
      } else {
        Print(L"No FACS\n");
      }
    }
  }

  Print(L"All done!\n");

done:
  CleanRangeCheckContext(&RangeCheck);
  return Status;
}

在KBL-R HDK 上的运行结果:

源代码和编译后的 EFI 程序可以在这里下载到:

参考:

  1. http://www.lab-z.com/acpishll/  介绍一个好用的 ACPIDUMP 工具
  2. https://github.com/andreiw/UefiToolsPkg/tree/master/Applications

Arduino 供电 Shield

Arduino Uno 可以使用 USB 供电或者 12V供电,但是对于展示来说,这样还是不太方便。为此,我设计了一个装有充放电模块的Shield,相当于集成了一个充电宝,可以对4.2v 电池进行充放电管理,同时提供5V电源输出。
本打算使用充电宝的方案进行制作,搜索一番发现零售的很少,估计是因为这种东西都是走量的。即便有芯片方案,也比买成品模块偏移不了多少。最终选择了下面这种模块,价格是 12.8元。

连接很简单,需要特别注意的是其中左侧有一个按钮,这是唤醒整个模块开始供电的开关。对于Arduino 控制来说,将这个位置拉低>100ms就是供电。比如,你的整体负载<100ma,这个模块会在20秒后停止供电,因此,你可以用一个IO每隔20秒拉低一次一次实现持续供电。

电路图如下:

PCB 如下

最终的实物:

需要注意的地方:
1.这里预留一个跳线帽的位置,使用时需要加一个跳线,这样的设计是为了如果很长时间不使用防止电量耗光

2.下面这个位置和 Uno 的USB口有冲突,在使用时务必小心短路

附件是电路图和PCB。
ZChargerShield

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 无关。

运行结果:

完整代码下载: