当前位置: 首页 > news >正文

Qemu镜像安全加密测试

文章目录

  • 简介
  • 1.已经过时的qemu自带的加密方式介绍
    • 1.1.创建secret uuid的xml
    • 1.2.产生uuid
    • 1.3.给secret赋值
    • 1.4.创建一个存储池
    • 1.5.在存储池中创建一个镜像
    • 1.6.在虚拟机中使用该镜像
  • 2.弃用以上加密方式
    • 2.1.原作者Daniel Berrange的观点
    • 2.2.Markus Armbruster更深入的操作
  • 3. LUKS(Linux Unified Key Setup)加密简介
  • 4.Libvirt对镜像进行LUKS加密
    • 4.1.需要的文件
  • 4.2.步骤
  • 4.3.结论
  • Reference

简介

本文介绍两种qemu镜像加密方式,一种是已经过时的方式,另外一种是luks方式,作为对比和学习使用。

1.已经过时的qemu自带的加密方式介绍

这种加密方式已经被原作者(Daniel P. Berrange berrange@redhat.com)废除(deprecated),本章节先介绍一下这种方式的基本操作和废除原因123,以避免日后使用使用方式或者研究思路有错误。

1.1.创建secret uuid的xml

# cat demo-secret.xml
<secret ephemeral='no' private='no'><uuid>0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f</uuid><usage type='volume'><volume>/home/VirtualMachines/demo.qcow2</volume></usage>
</secret>

ephemeral是短暂的意思,如果设置为yes,则vm重启之后,加密就会消失,这一般应用于有第三方加密管理的情况下,这里选择no。
privacy是表明是否libvirt可以给用户看到与该uuid关联的secret信息,如果设置为yes,就是writeonly的。
uuid就是一个secret的唯一标识,如果不指定uuid,下一步会随机生成一个。

1.2.产生uuid

# virsh secret-define demo-secret.xmlSecret 1a81f5b2-8403-7b23-c8d6-21ccc2f80d6f created

1.3.给secret赋值

# MYSECRET=`echo "open seseme" | base64`# virsh secret-set-value 0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f $MYSECRETSecret value set

base64是一种编码格式,这样,一个拥有唯一标识符uuid的secret就产生完整了。

1.4.创建一个存储池

#virsh pool-dumpxml VirtualMachines

<pool type='dir'><name>VirtualMachines</name><source></source><target><path>/home/VirtualMachines</path></target>
</pool>

这是一个目录类型的存储池,必须先用mkdir创建/home/VirtualMachines目录

#virsh pool-define xxxx.xml

1.5.在存储池中创建一个镜像

# cat demo-disk.xml
<volume><name>demo.qcow2</name><capacity>5368709120</capacity><target><format type='qcow2'/><encryption format='qcow'><secret type='passphrase' uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f'/></encryption></target>
</volume>
# virsh vol-create VirtualMachines demo-disk.xml
Vol demo.qcow2 created from demo-disk.xml

可以查看该demo.qcow2的信息

# file demo.qcow2 
demo.qcow2: QEMU QCOW Image (v2), 5368709120 bytes, AES-encrypted.

这里注意,该’passphrase’ 并没有按照例子设置具体值,也能正常工作。

1.6.在虚拟机中使用该镜像

# cat demo-guest.xml
<domain type='qemu'><name>demo</name><memory>500200</memory><vcpu>4</vcpu><os><type arch='i686' machine='pc'>hvm</type><kernel>/home/vmlinuz-PAE</kernel><initrd>/home/initrd-PAE.img</initrd><boot dev='hd'/></os><devices><emulator>/usr/bin/qemu-kvm</emulator><disk type='file' device='disk'><driver name='qemu' type='qcow2'/><source file='/home/VirtualMachines/demo.qcow2'/><target dev='hda' bus='ide'/><encryption format='qcow'><secret type='passphrase' uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f'/></encryption></disk><input type='tablet' bus='usb'/><input type='mouse' bus='ps2'/><graphics type='vnc' port='-1' autoport='yes'/></devices>
</domain>

这是一个空硬盘,可以直接用内核文件vmlinuz和initrd做PXE启动。

2.弃用以上加密方式

2.1.原作者Daniel Berrange的观点

A. AES-CBC加密算法本身很脆弱,容易受到选择明文攻击
B. 本实现直接用的1.3小节中的passphrase当作加密密钥,密钥的选择不安全,太短并且缺乏随机性
C. 当加密被入侵之后,没有机制可以修改密钥,镜像安全得不到保证

2.2.Markus Armbruster更深入的操作

首先创建三个文件4

[root@localhost home]# qemu-img create -f raw backing.img 4m
Formatting 'backing.img', fmt=raw size=4194304
[root@localhost home]# qemu-img create -f qcow2 -o encryption,backing_file=backing.img,backing_fmt=raw geheim.qcow2 4m
Formatting 'geheim.qcow2', fmt=qcow2 size=4194304 backing_file=backing.img backing_fmt=raw encryption=on cluster_size=65536 lazy_refcounts=off refcount_bits=16
[root@localhost home]# qemu-img create -f qcow2 -o backing_file=backing.img, backing_fmt=raw normal.qcow2 4m
Formatting 'normal.qcow2', fmt=qcow2 size=4194304 backing_file=backing.img backing_fmt=raw encryption=off cluster_size=65536 lazy_refcounts=off refcount_bits=16

这里qemu-img执行了三次,可以看到,出现了一个backing_file参数,如果“-o”选项中使用了backing_file这个选项来指定其后端镜像文件,那么这个创建的镜像文件仅记录与后端镜像文件的差异部分。后端镜像文件不会被修改,除非在QEMU monitor中使用“commit”命令或者使用“qemu-img commit”命令去手动提交这些改动。这种情况下,size参数不是必须需的,其值默认为后端镜像文件的大小。另外,直接使用“-b backfile”参数也与“-o backing_file=backfile”效果相同。
剩下的操作,现在的QEMU版本已经不支持,会提示报错

[root@localhost home]# qemu-system-x86_64 -nodefaults -display none -monitor stdio geheim.qcow2
qemu-system-x86_64: -monitor stdio: Use of AES-CBC encrypted qcow2 images is no longer supported in system emulators

剩下的分析可以查看附录[2],当前QEMU版本推荐的硬盘加密方式是LUKS(Linux Unified Key Setup)。

3. LUKS(Linux Unified Key Setup)加密简介

#用vmware添加一块新磁盘,/dev/sdc,然后进行加密
[root@localhost ~]#  cryptsetup -v -y -c aes-cbc-plain luksFormat /dev/sdcWARNING!
========
This will overwrite data on /dev/sdc irrevocably.Are you sure? (Type uppercase yes): YES
Enter passphrase: 
Verify passphrase: 
Command successful.
#映射分区,需要输入密码,之后就不用了
[root@localhost ~]# cryptsetup luksOpen /dev/sdc test_disk //把sdb1映射为test_disk
Enter passphrase for /dev/sdc: 
[root@localhost ~]# ll -d /dev/mapper/test_disk 
lrwxrwxrwx. 1 root root 7 Oct 19 02:02 /dev/mapper/test_disk -> ../dm-2
#查看映射分区状态
[root@localhost ~]# cryptsetup status /dev/mapper/
cl-root    cl-swap    control    test_disk  
[root@localhost ~]# cryptsetup status /dev/mapper/cl-root 
[root@localhost ~]# cryptsetup status /dev/mapper/cl-swap 
[root@localhost ~]# cryptsetup status /dev/mapper/control 
/dev/mapper/control is inactive.
[root@localhost ~]# cryptsetup status /dev/mapper/test_disk 
/dev/mapper/test_disk is active.type:    LUKS1cipher:  aes-cbc-plainkeysize: 256 bitsdevice:  /dev/sdcoffset:  4096 sectorssize:    10481664 sectorsmode:    read/write

可以看到,未加密的映射分区是没有加密信息的,其中有个/dev/mapper/control不知道是做什么的,以后再研究。可以看到/dev/mapper/test_disk分区的加密信息,使用的aes-cbc-plain加密算法,用qemu-img命令生成的加密镜像,从qemu报错可以看出,现在的qemu版本不支持这种aes-cbc算法,目前无法确定用LUKS生成的加密镜像如果用这种算法,qemu会不会报错,猜测是会报错的,后面再进行验证。

#挂载分区
[root@localhost home]# mkfs.xfs /dev/mapper/test_disk 
meta-data=/dev/mapper/test_disk  isize=512    agcount=4, agsize=327552 blks=                       sectsz=512   attr=2, projid32bit=1=                       crc=1        finobt=0, sparse=0
data     =                       bsize=4096   blocks=1310208, imaxpct=25=                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal log           bsize=4096   blocks=2560, version=2=                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
[root@localhost home]# mount /dev/sdc /home/crypto_test/
mount: unknown filesystem type 'crypto_LUKS'  //不能直接挂载,要挂载在映射设备上
[root@localhost home]# mount /dev/mapper/test_disk /home/crypto_test/
[root@localhost home]# df -h
Filesystem             Size  Used Avail Use% Mounted on
/dev/mapper/test_disk  5.0G   33M  5.0G   1% /home/crypto_test

挂载成功。

#关闭映射,先卸载后关闭
[root@localhost home]# umount /home/crypto_test/
[root@localhost home]# cryptsetup status /dev/mapper/test_disk 
/dev/mapper/test_disk is active.type:    LUKS1cipher:  aes-cbc-plainkeysize: 256 bitsdevice:  /dev/sdcoffset:  4096 sectorssize:    10481664 sectorsmode:    read/write
[root@localhost home]# cryptsetup luksClose test_disk
[root@localhost home]# cryptsetup status /dev/mapper/test_disk 
/dev/mapper/test_disk is inactive.

4.Libvirt对镜像进行LUKS加密

4.1.需要的文件

参考第1节,步骤和需要的文件几乎完全一样,只是加密方式不同。本节只标出不同的地方,其他步骤参考第1节5
需要的文件为:demo-disk.xml demo-pool.xml demo-secret.xml

#demo-secret.xml
<secret ephemeral='no' private='yes'><uuid>0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f</uuid><usage type='volume'><volume>/home/crypto-test/twofish.luks</volume></usage>
</secret>#demo-pool.xml
<pool type='dir'><name>test-pool</name><source></source><target><path>/home/crypto-test</path></target>
</pool>#demo-disk.xml
<volume><name>twofish.luks</name><capacity>3111111111</capacity><target><format type='raw'/><encryption format='luks'><secret type='passphrase' uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f'/><cipher name='twofish' size='256' mode='cbc' hash='sha256'/><ivgen name='plain64' hash='sha256'/></encryption></target>
</volume>

差异在于

<format type='raw'/><encryption format='luks'>

4.2.步骤

#查看镜像文件格式

[root@localhost crypto-test]# file twofish.luks 
twofish.luks: LUKS encrypted file, ver 1 [twofish, cbc-plain64:sha256, sha256] UUID: c46eef30-b6c2-473a-959d-84d941b0d893

#虚拟机disk配置

<disk type='file' device='disk'><driver name='qemu' type='raw'/><source file='/home/crypto-test/twofish.luks'/><backingStore/><target dev='hda' bus='ide'/><encryption format='luks'><secret type='passphrase' uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f'/></encryption><alias name='ide0-0-0'/><address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<disk type='file' device='cdrom'><driver name='qemu' type='raw'/><source file='/home/ubuntu-14.04.1-server-amd64-20170531.iso'/><backingStore/><target dev='hdc' bus='ide'/><readonly/><alias name='ide0-1-0'/><address type='drive' controller='0' bus='1' target='0' unit='0'/>
</disk>

在使用加密镜像的时候,由于实验中建立的是一个空镜像,里面没有操作系统,因此要带上一个cdrom,系统安装好之后,测试把<encryption format='luks'>删除,会找不到硬盘,加密生效。
在这里插入图片描述

故意将passphrase的最后一个字母从f改成e,再启动虚拟机

<encryption format='luks'><secret type='passphrase' uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6e'/>
</encryption>
[root@localhost home]# virsh start vm_test_2
error: Failed to start domain vm_test_2
error: Secret not found: no secret with matching uuid '0a81f5b2-8403-7b23-c8d6-21ccc2f80d6e'

4.3.结论

使用了LUKS之后,libvirt配置的passphrase不再用来当作生成加密密钥,仅仅用来解锁加密密钥,并且可以随时更改[3]。并且在demo-disk.xml的encryption标签里面,还可以配置成其他的选项来选择加密算法和密钥生成方式。

Reference


  1. Using QCow2 disk encryption with libvirt in Fedora 12(deprecated) ↩︎

  2. [Qemu-devel] [PULL 03/17] block: Deprecate QCOW/QCOW2 encryption ↩︎

  3. QEMU QCow2 built-in encryption: just say no. Deprecated now, to be deleted soon ↩︎

  4. KVM虚拟机磁盘镜像文件管理命令qemu-img介绍及简单使用总结 ↩︎

  5. Secret XML format ↩︎

相关文章:

Qemu镜像安全加密测试

文章目录 简介1.已经过时的qemu自带的加密方式介绍1.1.创建secret uuid的xml1.2.产生uuid1.3.给secret赋值1.4.创建一个存储池1.5.在存储池中创建一个镜像1.6.在虚拟机中使用该镜像 2.弃用以上加密方式2.1.原作者Daniel Berrange的观点2.2.Markus Armbruster更深入的操作 3. LU…...

Ubuntu 18.04 LTS中cmake-gui编译opencv-3.4.16并供Qt Creator调用

一、安装opencv 1.下载opencv-3.4.16的源码并解压 2.在解压后的文件夹内新建文件夹build以及opencv_install 3.启动cmake-gui并设置 sudo cmake-gui&#xff08;1&#xff09;设置界面中source及build路径 &#xff08;2&#xff09;点击configure&#xff0c;选择第一个def…...

SpringBoot (2) yaml,整合项目

目录 1 YAML配置文件 1.1 书写规则 1.2 代码示例 1.3 用yaml进行复杂数据绑定 2 整合日志 2.1 日志配置 3 整合web 3.1 默认配置 3.2 web应用开发方式 3.2.1 全自动 3.2.2 全手动 3.2.3 手自一体(推荐) 4 整合mybatis 4.1 导包 4.2 application.yaml 4.3 dao接…...

django建站过程(2)创建第一个应用程序页面

创建第一个应用程序页面 设置第一个页面【settings.py,urls.py,views.py】settings.pyurls.pyviews.py django是由一系列应用程序组成&#xff0c;协同工作&#xff0c;让项目成为一个整体。前面已创建了一个应用程序baseapp,使用的命令 python manage.py startapp baseapps这…...

竞赛 深度学习人体语义分割在弹幕防遮挡上的实现 - python

文章目录 1 前言1 课题背景2 技术原理和方法2.1基本原理2.2 技术选型和方法 3 实例分割4 实现效果5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习人体语义分割在弹幕防遮挡上的应用 该项目较为新颖&#xff0c;适合作为竞…...

网络编程开发及实战(下)

一、IO模型 一、基本概念 &#xff08;一&#xff09;I/O基本概念 1、基本概念 1&#xff09;一个完整I/O分为两个阶段&#xff1a; 用户进程空间->内核空间 内核空间->设备空间&#xff08;磁盘、网卡&#xff09; 2&#xff09;内存I/O&#xff08;无名管道&…...

(H5轮播)vue一个轮播里显示多个内容/一屏展示两个半内容

效果图 : html: <div class"content"><van-swipeclass"my-swipe com-long-swipe-indicator":autoplay"2500"indicator-color"#00C4FF"><van-swipe-itemclass"flex-row-wrap"v-for"(items, index) in M…...

【Proteus仿真】【Arduino单片机】蜂鸣器

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用蜂鸣器等。 主要功能&#xff1a; 系统运行后&#xff0c;蜂鸣器播放音乐。 二、软件设计 /* 作者&#xff1a;嗨小易&#xff08;QQ&#x…...

seatunnel web ui 构建时报错

报错内容如下 Failed to execute goal com.diffplug.spotless:spotless-maven-plugin:2.29.0:check (default) on project seatunnel-web: The following files had format violations:Failed to execute goal com.diffplug.spotless:spotless-maven-plugin:2.29.0:check (defa…...

Js使用ffmpeg在视频中添加png或gif

Js使用ffmpeg在视频中添加png或gif ffmpeg 使用场景是需要在web端对视频进行编辑 添加图片和gif。 注意: 以下所有的使用案例均基于vue3 setup。 同时由于ffmpeg版本不同会导致使用的api不同&#xff0c;使用案例前需要注意ffmpeg版本问题。 如果使用的是0.12需要使用新的…...

多线程 Leetcode 打印零与奇偶数

现有函数 printNumber 可以用一个整数参数调用&#xff0c;并输出该整数到控制台。 例如&#xff0c;调用 printNumber(7) 将会输出 7 到控制台。 给你类 ZeroEvenOdd 的一个实例&#xff0c;该类中有三个函数&#xff1a;zero、even 和 odd 。ZeroEvenOdd 的相同实例将会传递…...

杭电oj--数列有序

有n(n<100)个整数&#xff0c;已经按照从小到大顺序排列好&#xff0c;现在另外给一个整数x&#xff0c;请将该数插入到序列中&#xff0c;并使新的序列仍然有序。 输入数据包含多个测试实例&#xff0c;每组数据由两行组成&#xff0c;第一行是n和m&#xff0c;第二行是已…...

PHPEXCEL解决行数超过65536不显示问题

起因自然是导出数据到excel文件时&#xff0c;数据缺少现象。 百度讲解是将xls文件另存为xlsx文件。 除了这里的原因&#xff0c;还有一点是phpExcel存在两个写入类PHPExcel_Writer_Excel2007和PHPExcel_Writer_Excel5&#xff0c;而只有PHPExcel_Writer_Excel2007支持超过65…...

新媒体时代如何做好新型的网络口碑营销?

从人类开始交换商品的时代开始&#xff0c;口碑营销就已经存在&#xff0c;是靠口耳传播的营销方式。小马识途认为进入当今移动互联网时代&#xff0c;口碑营销又有了新的发展&#xff0c;网络口碑营销推广开始普及。营销人员将传统口碑营销与移动互联网营销相结合&#xff0c;…...

MySQL中InnoDB插入缓冲区(Insert Buffer)

一、插入缓冲区的基本原理 插入缓冲区&#xff08;Insert Buffer&#xff0c;也称作 Change Buffer&#xff09;&#xff0c;是InnoDB存储引擎的一种内部机制&#xff0c;它允许系统将对非聚集索引页的写操作&#xff08;例如插入、删除和更新&#xff09;暂时缓存在内存中&am…...

VUE前端判断是电脑端还是移动端

背景需求 ruoyi框架&#xff0c;前后端分离。现在要在用户访问的时候根据不同的设备跳转到不同的登录页面。 教程 router/index.js 修改src/router/index.js&#xff0c;在这里增加自己的要跳转的页面 permission.js 在白名单中添加自己的登录页面 增加以下识别的代码 le…...

OpenGL —— 2.8、漫游之摄像机飞行移动(附源码,glfw+glad)

源码效果 C源码 纹理图片 需下载stb_image.h这个解码图片的库&#xff0c;该库只有一个头文件。 具体代码&#xff1a; vertexShader.glsl #version 330 corelayout(location 0) in vec3 aPos; layout(location 1) in vec2 aUV;out vec2 outUV;uniform mat4 _modelMatrix; …...

AM@麦克劳林公式逼近以及误差分析

abstract 麦克劳林公式及其近似表示的应用误差估计和分析 Lagrange型泰勒公式的估计误差 由Lagrange型余项泰勒公式可知,多项式 p n ( x ) p_n(x) pn​(x)近似表达函数 f ( x ) f(x) f(x)时,其误差为 ∣ R n ( x ) ∣ |R_{n}(x)| ∣Rn​(x)∣ R n ( x ) R_{n}(x) Rn​(x) f …...

gitlab 离线安装问题解决:NOKEY,signature check fail

1&#xff0c;rpm安装gitlab问题 test1:/opt # rpm -ivh gitlab-ce-16.0.3-ce.0.el7.x86_64.rpm --force warning: gitlab-ce-16.0.3-ce.0.el7.x86_64.rpm: Header V4 RSA/SHA1 Signature, key ID f27eab47: NOKEY error: [upel]: gitlab-ce NOKEY error: [upel]: gitlab-ce …...

uniapp使用uQRCode绘制二维码,下载到本地,调起微信扫一扫二维码核销

1.效果 2.在utils文件夹下创建uqrcode.js // uqrcode.js //--------------------------------------------------------------------- // github https://github.com/Sansnn/uQRCode //---------------------------------------------------------------------let uQRCode {…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...